Updated/Introduced: 1. Spring security (currently disabled seen in SecurityConfig.java) 2. Renamed Coloumn names to be more aligned with best practices 3. Return DTO instead of User Object to request

This commit is contained in:
Mika Bomm 2024-09-06 20:01:42 +02:00
parent 10f417e961
commit c37139287f
7 changed files with 146 additions and 46 deletions

View file

@ -19,10 +19,11 @@ repositories {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Argon2 password hashing // Argon2 password hashing
implementation 'org.springframework.security:spring-security-crypto:6.3.3' implementation 'org.springframework.security:spring-security-crypto:6.3.3'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
// Dotenv manager // Dotenv manager
implementation 'io.github.cdimascio:java-dotenv:5.2.2' implementation 'io.github.cdimascio:java-dotenv:5.2.2'
@ -30,8 +31,14 @@ dependencies {
// Minio S3 Storage // Minio S3 Storage
implementation 'io.minio:minio:8.5.12' implementation 'io.minio:minio:8.5.12'
// JWT token libary (jjwt)
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' // or 'io.jsonwebtoken:jjwt-gson:0.12.6' for gson
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
} }

View file

@ -0,0 +1,23 @@
package com.mixel.docusphere.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Disable CSRF protection
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // Allow all requests without authentication
);
return http.build();
}
}

View file

@ -23,24 +23,24 @@ public class UserController {
} }
@GetMapping @GetMapping
public List<User> getAllUsers() { public List<UserDTO> getAllUsers() {
return userService.findAll(); return userService.findAll();
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable UUID id) { public ResponseEntity<UserDTO> getUserById(@PathVariable UUID id) {
Optional<User> user = userService.findById(id); Optional<UserDTO> user = userService.findById(id);
return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
} }
@PostMapping @PostMapping
public User createUser(@RequestBody UserDTO userDTO) { public UserDTO createUser(@RequestBody UserDTO userDTO) {
return userService.save(userDTO); return userService.save(userDTO);
} }
@PutMapping("/{id}") @PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable UUID id, @RequestBody UserDTO userDTO) { public ResponseEntity<UserDTO> updateUser(@PathVariable UUID id, @RequestBody UserDTO userDTO) {
Optional<User> updatedUser = userService.update(id, userDTO); Optional<UserDTO> updatedUser = userService.update(id, userDTO);
return updatedUser.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); return updatedUser.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
} }

View file

@ -1,10 +1,14 @@
package com.mixel.docusphere.dto; package com.mixel.docusphere.dto;
import java.time.LocalDateTime;
public class UserDTO { public class UserDTO {
private String username; private String username;
private String name; private String name;
private String email; private String email;
private String password; private String password; // Only Include for creation and update
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters // Getters and Setters
public String getUsername() { public String getUsername() {
@ -38,4 +42,20 @@ public class UserDTO {
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
} }
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
} }

View file

@ -1,44 +1,45 @@
package com.mixel.docusphere.entity; package com.mixel.docusphere.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID; import java.util.UUID;
@Entity @Entity
@Table(name = "Users") @Table(name = "Users")
public class User { public class User implements UserDetails {
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "UserID", updatable = false, nullable = false) @Column(name = "user_id", updatable = false, nullable = false)
private UUID userId; private UUID userId;
@Column(name = "Username", nullable = false, unique = true) @Column(name = "username", nullable = false, unique = true)
private String username; private String username;
@Column(name = "Name", nullable = false) @Column(name = "name", nullable = false)
private String name; private String name;
@Column(name = "Email", nullable = false, unique = true) @Column(name = "email", nullable = false, unique = true)
private String email; private String email;
@JsonIgnore @JsonIgnore
@Column(name = "PasswordHash", nullable = false) @Column(name = "password_hash", nullable = false)
private String passwordHash; private String passwordHash;
@JsonIgnore
@Column(name = "PasswordSalt", nullable = false)
private String passwordSalt;
@CreationTimestamp @CreationTimestamp
@Column(name = "CreatedAt", nullable = false, updatable = false) @Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@UpdateTimestamp @UpdateTimestamp
@Column(name = "UpdatedAt", nullable = false) @Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@PrePersist @PrePersist
@ -93,14 +94,6 @@ public class User {
this.passwordHash = passwordHash; this.passwordHash = passwordHash;
} }
public String getPasswordSalt() {
return passwordSalt;
}
public void setPasswordSalt(String passwordSalt) {
this.passwordSalt = passwordSalt;
}
public LocalDateTime getCreatedAt() { public LocalDateTime getCreatedAt() {
return createdAt; return createdAt;
} }
@ -116,4 +109,38 @@ public class User {
public void setUpdatedAt(LocalDateTime updatedAt) { public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt; this.updatedAt = updatedAt;
} }
// UserDetails interface methods
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// Return the authorities granted to the user
return Collections.emptyList(); // Implement this based on your requirements
}
@JsonIgnore
@Override
public String getPassword() {
return passwordHash;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
} }

View file

@ -0,0 +1,26 @@
package com.mixel.docusphere.mapper;
import com.mixel.docusphere.dto.UserDTO;
import com.mixel.docusphere.entity.User;
public class UserMapper {
public static UserDTO toDTO(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setUsername(user.getUsername());
userDTO.setName(user.getName());
userDTO.setEmail(user.getEmail());
userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setUpdatedAt(user.getUpdatedAt());
return userDTO;
}
public static User toEntity(UserDTO userDTO) {
User user = new User();
user.setUsername(userDTO.getUsername());
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
return user;
}
}

View file

@ -2,14 +2,15 @@ package com.mixel.docusphere.service;
import com.mixel.docusphere.dto.UserDTO; import com.mixel.docusphere.dto.UserDTO;
import com.mixel.docusphere.entity.User; import com.mixel.docusphere.entity.User;
import com.mixel.docusphere.mapper.UserMapper;
import com.mixel.docusphere.repository.UserRepository; import com.mixel.docusphere.repository.UserRepository;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Service @Service
public class UserService { public class UserService {
@ -24,25 +25,24 @@ public class UserService {
this.userRepository = userRepository; this.userRepository = userRepository;
} }
public List<User> findAll() { public List<UserDTO> findAll() {
return userRepository.findAll(); return userRepository.findAll().stream()
.map(UserMapper::toDTO)
.collect(Collectors.toList());
} }
public Optional<User> findById(UUID id) { public Optional<UserDTO> findById(UUID id) {
return userRepository.findById(id); return userRepository.findById(id)
.map(UserMapper::toDTO);
} }
public User save(UserDTO userDTO) { public UserDTO save(UserDTO userDTO) {
User user = new User(); User user = UserMapper.toEntity(userDTO);
user.setUsername(userDTO.getUsername());
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
isPasswordAlreadySet(userDTO, user); isPasswordAlreadySet(userDTO, user);
return userRepository.save(user); return UserMapper.toDTO(userRepository.save(user));
} }
public Optional<User> update(UUID id, UserDTO userDTO){ public Optional<UserDTO> update(UUID id, UserDTO userDTO){
Optional<User> userOptional = userRepository.findById(id); Optional<User> userOptional = userRepository.findById(id);
if (userOptional.isPresent()) { if (userOptional.isPresent()) {
User user = userOptional.get(); User user = userOptional.get();
@ -51,17 +51,14 @@ public class UserService {
user.setEmail(userDTO.getEmail()); user.setEmail(userDTO.getEmail());
isPasswordAlreadySet(userDTO, user); isPasswordAlreadySet(userDTO, user);
return Optional.of(userRepository.save(user)); return Optional.of(UserMapper.toDTO(userRepository.save(user)));
} }
return Optional.empty(); return Optional.empty();
} }
private void isPasswordAlreadySet(UserDTO userDTO, User user) { private void isPasswordAlreadySet(UserDTO userDTO, User user) {
if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) { if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) {
final String salt = KeyGenerators.string().generateKey(); user.setPasswordHash(passwordEncoder.encode(userDTO.getPassword()));
user.setPasswordSalt(salt);
final String saltedPassword = salt + userDTO.getPassword();
user.setPasswordHash(passwordEncoder.encode(saltedPassword));
} }
} }