commit
43faab805b
@ -0,0 +1,38 @@ |
||||
target/ |
||||
!.mvn/wrapper/maven-wrapper.jar |
||||
!**/src/main/**/target/ |
||||
!**/src/test/**/target/ |
||||
|
||||
### IntelliJ IDEA ### |
||||
.idea/modules.xml |
||||
.idea/jarRepositories.xml |
||||
.idea/compiler.xml |
||||
.idea/libraries/ |
||||
*.iws |
||||
*.iml |
||||
*.ipr |
||||
|
||||
### Eclipse ### |
||||
.apt_generated |
||||
.classpath |
||||
.factorypath |
||||
.project |
||||
.settings |
||||
.springBeans |
||||
.sts4-cache |
||||
|
||||
### NetBeans ### |
||||
/nbproject/private/ |
||||
/nbbuild/ |
||||
/dist/ |
||||
/nbdist/ |
||||
/.nb-gradle/ |
||||
build/ |
||||
!**/src/main/**/build/ |
||||
!**/src/test/**/build/ |
||||
|
||||
### VS Code ### |
||||
.vscode/ |
||||
|
||||
### Mac OS ### |
||||
.DS_Store |
@ -0,0 +1,8 @@ |
||||
# Default ignored files |
||||
/shelf/ |
||||
/workspace.xml |
||||
# Editor-based HTTP Client requests |
||||
/httpRequests/ |
||||
# Datasource local storage ignored files |
||||
/dataSources/ |
||||
/dataSources.local.xml |
@ -0,0 +1,7 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="Encoding"> |
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> |
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> |
||||
</component> |
||||
</project> |
@ -0,0 +1,14 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="ExternalStorageConfigurationManager" enabled="true" /> |
||||
<component name="MavenProjectsManager"> |
||||
<option name="originalFiles"> |
||||
<list> |
||||
<option value="$PROJECT_DIR$/pom.xml" /> |
||||
</list> |
||||
</option> |
||||
</component> |
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> |
||||
<output url="file://$PROJECT_DIR$/out" /> |
||||
</component> |
||||
</project> |
@ -0,0 +1,6 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="VcsDirectoryMappings"> |
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> |
||||
</component> |
||||
</project> |
@ -0,0 +1,85 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<groupId>org.example</groupId> |
||||
<artifactId>demoNeo4j</artifactId> |
||||
<version>1.0-SNAPSHOT</version> |
||||
|
||||
<properties> |
||||
<java.version>17</java.version> |
||||
<maven.compiler.source>${java.version}</maven.compiler.source> |
||||
<maven.compiler.target>${java.version}</maven.compiler.target> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
</properties> |
||||
|
||||
<dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-dependencies</artifactId> |
||||
<version>3.0.0</version> |
||||
<type>pom</type> |
||||
<scope>import</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</dependencyManagement> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-web</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-data-neo4j</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.neo4j.driver</groupId> |
||||
<artifactId>neo4j-java-driver</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-devtools</artifactId> |
||||
<scope>runtime</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-security</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>me.paulschwarz</groupId> |
||||
<artifactId>spring-dotenv</artifactId> |
||||
<version>3.0.0</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-jdbc</artifactId> |
||||
<version>6.1.8</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
|
||||
</dependencies> |
||||
|
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
@ -0,0 +1,11 @@ |
||||
package org.example; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
||||
@SpringBootApplication |
||||
public class Main { |
||||
public static void main(String[] args) { |
||||
SpringApplication.run(Main.class, args); |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
package org.example.config; |
||||
|
||||
import org.example.services.NeoUserDetailsService; |
||||
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.config.annotation.web.configurers.AbstractHttpConfigurer; |
||||
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.web.SecurityFilterChain; |
||||
import org.springframework.web.cors.CorsConfiguration; |
||||
import org.springframework.web.cors.CorsConfigurationSource; |
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
@Configuration |
||||
@EnableWebSecurity |
||||
public class SecurityConfig { |
||||
private final NeoUserDetailsService neoUserDetailsService; |
||||
|
||||
public SecurityConfig(NeoUserDetailsService neoUserDetailsService) { |
||||
this.neoUserDetailsService = neoUserDetailsService; |
||||
} |
||||
|
||||
@Bean |
||||
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { |
||||
return httpSecurity |
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) |
||||
.csrf(AbstractHttpConfigurer::disable) |
||||
.cors(Customizer.withDefaults()) |
||||
.authorizeHttpRequests(auth -> auth |
||||
.requestMatchers( |
||||
"/api/v1/auth/me" |
||||
).authenticated() |
||||
.anyRequest().permitAll() |
||||
) |
||||
.userDetailsService(neoUserDetailsService) |
||||
.httpBasic(Customizer.withDefaults()) |
||||
.build(); |
||||
} |
||||
|
||||
@Bean |
||||
PasswordEncoder passwordEncoder() { |
||||
return new BCryptPasswordEncoder(); |
||||
} |
||||
|
||||
@Bean |
||||
CorsConfigurationSource corsConfigurationSource() { |
||||
CorsConfiguration configuration = new CorsConfiguration(); |
||||
|
||||
// TODO: make sure that the origin list comes from an environment file.
|
||||
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3001", "http://127.0.0.1:3000")); |
||||
configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH", "PUT", "DELETE", "OPTIONS", "HEAD")); |
||||
configuration.setAllowCredentials(true); |
||||
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Requestor-Type", "Content-Type")); |
||||
configuration.setExposedHeaders(Arrays.asList("X-Get-Header")); |
||||
configuration.setMaxAge(3600L); |
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); |
||||
source.registerCorsConfiguration("/**", configuration); |
||||
|
||||
return source; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,34 @@ |
||||
package org.example.controllers; |
||||
|
||||
import org.example.models.User; |
||||
import org.example.objects.UserDTO; |
||||
import org.example.requests.CreateUserRequest; |
||||
import org.example.services.UserService; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import java.security.Principal; |
||||
@RestController |
||||
@RequestMapping("/api/v1/auth") |
||||
public class UserController { |
||||
private final UserService userService; |
||||
|
||||
public UserController(UserService userService) { |
||||
this.userService = userService; |
||||
} |
||||
|
||||
@GetMapping("/me") |
||||
public String loggedInUserDetails(Principal principal) { |
||||
return principal.getName(); |
||||
} |
||||
|
||||
@PostMapping("/register") |
||||
public ResponseEntity<UserDTO> signUp(@RequestBody CreateUserRequest request) { |
||||
User user = userService.createUser(request); |
||||
|
||||
UserDTO responseUser = new UserDTO(user.getName(), user.getUsername(), user.getRoles()); |
||||
|
||||
return new ResponseEntity<>(responseUser, HttpStatus.CREATED); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
package org.example.models; |
||||
|
||||
import org.springframework.data.neo4j.core.schema.GeneratedValue; |
||||
import org.springframework.data.neo4j.core.schema.Id; |
||||
import org.springframework.data.neo4j.core.schema.Node; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
|
||||
@Node |
||||
public class User implements UserDetails { |
||||
@Id @GeneratedValue |
||||
private Long id; |
||||
private String name; |
||||
private String username; |
||||
private String password; |
||||
private String roles; |
||||
|
||||
public User() { |
||||
} |
||||
|
||||
public Long getId() { |
||||
return id; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public String getUsername() { |
||||
return username; |
||||
} |
||||
|
||||
@Override |
||||
public String getPassword() { |
||||
return password; |
||||
} |
||||
|
||||
public String getRoles() { |
||||
return roles; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public void setUsername(String username) { |
||||
this.username = username; |
||||
} |
||||
|
||||
public void setPassword(String password) { |
||||
this.password = password; |
||||
} |
||||
|
||||
public void setRoles(String roles) { |
||||
this.roles = roles; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAccountNonExpired() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isAccountNonLocked() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isCredentialsNonExpired() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEnabled() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public Collection<? extends GrantedAuthority> getAuthorities() { |
||||
return Arrays.stream(roles.split(",")) |
||||
.map(SimpleGrantedAuthority::new) |
||||
.toList(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "User{" + |
||||
"name='" + name + '\'' + |
||||
", username='" + username + '\'' + |
||||
", roles='" + roles + '\'' + |
||||
'}'; |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
package org.example.objects; |
||||
|
||||
public class UserDTO { |
||||
private String name; |
||||
private String username; |
||||
private String roles; |
||||
|
||||
public UserDTO(String name, String username, String roles) { |
||||
this.name = name; |
||||
this.username = username; |
||||
this.roles = roles; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
public String getUsername() { |
||||
return username; |
||||
} |
||||
public void setUsername(String username) { |
||||
this.username = username; |
||||
} |
||||
public String getRoles() { |
||||
return roles; |
||||
} |
||||
public void setRoles(String roles) { |
||||
this.roles = roles; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package org.example.queryresults; |
||||
|
||||
|
||||
import org.example.models.User; |
||||
|
||||
public class CourseEnrolmentQueryResult { |
||||
private User user; |
||||
|
||||
public CourseEnrolmentQueryResult() { |
||||
} |
||||
|
||||
public User getUser() { |
||||
return user; |
||||
} |
||||
|
||||
public void setUser(User user) { |
||||
this.user = user; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package org.example.repositories; |
||||
|
||||
import org.example.models.User; |
||||
import org.example.queryresults.CourseEnrolmentQueryResult; |
||||
import org.springframework.data.neo4j.repository.Neo4jRepository; |
||||
import org.springframework.data.neo4j.repository.query.Query; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public interface UserRepository extends Neo4jRepository<User, Long> { |
||||
Optional<User> findUserByUsername(String username); |
||||
|
||||
@Query("MATCH (user:User), (course:Course) WHERE user.username = $username AND course.identifier = $identifier " + |
||||
"RETURN EXISTS((user)-[:ENROLLED_IN]->(course))") |
||||
Boolean findEnrolmentStatus(String username, String identifier); |
||||
|
||||
@Query("MATCH (user:User), (course:Course) WHERE user.username = $username AND course.identifier = $identifier " + |
||||
"CREATE (user)-[:ENROLLED_IN]->(course) RETURN user, course") |
||||
CourseEnrolmentQueryResult createEnrolmentRelationship(String username, String identifier); |
||||
} |
@ -0,0 +1,49 @@ |
||||
package org.example.requests; |
||||
|
||||
public class CreateUserRequest { |
||||
private String name; |
||||
private String username; |
||||
private String password; |
||||
private String roles; |
||||
|
||||
public CreateUserRequest() { |
||||
} |
||||
public CreateUserRequest(String name, String username, String password, String roles) { |
||||
this.name = name; |
||||
this.username = username; |
||||
this.password = password; |
||||
this.roles = roles; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getUsername() { |
||||
return username; |
||||
} |
||||
|
||||
public void setUsername(String username) { |
||||
this.username = username; |
||||
} |
||||
|
||||
public String getPassword() { |
||||
return password; |
||||
} |
||||
|
||||
public void setPassword(String password) { |
||||
this.password = password; |
||||
} |
||||
|
||||
public String getRoles() { |
||||
return roles; |
||||
} |
||||
|
||||
public void setRoles(String roles) { |
||||
this.roles = roles; |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package org.example.services; |
||||
|
||||
import org.example.repositories.UserRepository; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
@Service |
||||
public class NeoUserDetailsService implements UserDetailsService { |
||||
|
||||
private final UserRepository userRepository; |
||||
|
||||
public NeoUserDetailsService(UserRepository userRepository) { |
||||
this.userRepository = userRepository; |
||||
} |
||||
|
||||
@Override |
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||
return userRepository |
||||
.findUserByUsername(username) |
||||
.orElseThrow(() -> new UsernameNotFoundException("Username not found" + username)); |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
package org.example.services; |
||||
|
||||
import org.example.models.User; |
||||
import org.example.repositories.UserRepository; |
||||
import org.example.requests.CreateUserRequest; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
@Service |
||||
public class UserService { |
||||
private final UserRepository userRepository; |
||||
|
||||
private final PasswordEncoder passwordEncoder; |
||||
|
||||
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { |
||||
this.userRepository = userRepository; |
||||
this.passwordEncoder = passwordEncoder; |
||||
} |
||||
|
||||
public User createUser(CreateUserRequest request) { |
||||
User user = new User(); |
||||
|
||||
user.setName(request.getName()); |
||||
// TODO: make sure that this username doesn't exist.
|
||||
user.setUsername(request.getUsername()); |
||||
user.setRoles(request.getRoles()); |
||||
user.setPassword(passwordEncoder.encode(request.getPassword())); |
||||
|
||||
userRepository.save(user); |
||||
|
||||
return user; |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
spring.neo4j.uri=neo4j://localhost:7687 |
||||
spring.neo4j.authentication.username=neo4j |
||||
spring.neo4j.authentication.password=12345678 |
@ -0,0 +1 @@ |
||||
Subproject commit 81ad3d154b3b36b98dc5c1179c7e7bfcd7a0b1b0 |
Loading…
Reference in new issue