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 {
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'
// Argon2 password hashing
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
implementation 'io.github.cdimascio:java-dotenv:5.2.2'
@ -30,8 +31,14 @@ dependencies {
// Minio S3 Storage
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'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
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
public List<User> getAllUsers() {
public List<UserDTO> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable UUID id) {
Optional<User> user = userService.findById(id);
public ResponseEntity<UserDTO> getUserById(@PathVariable UUID id) {
Optional<UserDTO> user = userService.findById(id);
return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public User createUser(@RequestBody UserDTO userDTO) {
public UserDTO createUser(@RequestBody UserDTO userDTO) {
return userService.save(userDTO);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable UUID id, @RequestBody UserDTO userDTO) {
Optional<User> updatedUser = userService.update(id, userDTO);
public ResponseEntity<UserDTO> updateUser(@PathVariable UUID id, @RequestBody UserDTO userDTO) {
Optional<UserDTO> updatedUser = userService.update(id, userDTO);
return updatedUser.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}

View file

@ -1,10 +1,14 @@
package com.mixel.docusphere.dto;
import java.time.LocalDateTime;
public class UserDTO {
private String username;
private String name;
private String email;
private String password;
private String password; // Only Include for creation and update
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public String getUsername() {
@ -38,4 +42,20 @@ public class UserDTO {
public void setPassword(String 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;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
@Entity
@Table(name = "Users")
public class User {
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "UserID", updatable = false, nullable = false)
@Column(name = "user_id", updatable = false, nullable = false)
private UUID userId;
@Column(name = "Username", nullable = false, unique = true)
@Column(name = "username", nullable = false, unique = true)
private String username;
@Column(name = "Name", nullable = false)
@Column(name = "name", nullable = false)
private String name;
@Column(name = "Email", nullable = false, unique = true)
@Column(name = "email", nullable = false, unique = true)
private String email;
@JsonIgnore
@Column(name = "PasswordHash", nullable = false)
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@JsonIgnore
@Column(name = "PasswordSalt", nullable = false)
private String passwordSalt;
@CreationTimestamp
@Column(name = "CreatedAt", nullable = false, updatable = false)
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "UpdatedAt", nullable = false)
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
@ -93,14 +94,6 @@ public class User {
this.passwordHash = passwordHash;
}
public String getPasswordSalt() {
return passwordSalt;
}
public void setPasswordSalt(String passwordSalt) {
this.passwordSalt = passwordSalt;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
@ -116,4 +109,38 @@ public class User {
public void setUpdatedAt(LocalDateTime 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.entity.User;
import com.mixel.docusphere.mapper.UserMapper;
import com.mixel.docusphere.repository.UserRepository;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class UserService {
@ -24,25 +25,24 @@ public class UserService {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
public List<UserDTO> findAll() {
return userRepository.findAll().stream()
.map(UserMapper::toDTO)
.collect(Collectors.toList());
}
public Optional<User> findById(UUID id) {
return userRepository.findById(id);
public Optional<UserDTO> findById(UUID id) {
return userRepository.findById(id)
.map(UserMapper::toDTO);
}
public User save(UserDTO userDTO) {
User user = new User();
user.setUsername(userDTO.getUsername());
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
public UserDTO save(UserDTO userDTO) {
User user = UserMapper.toEntity(userDTO);
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);
if (userOptional.isPresent()) {
User user = userOptional.get();
@ -51,17 +51,14 @@ public class UserService {
user.setEmail(userDTO.getEmail());
isPasswordAlreadySet(userDTO, user);
return Optional.of(userRepository.save(user));
return Optional.of(UserMapper.toDTO(userRepository.save(user)));
}
return Optional.empty();
}
private void isPasswordAlreadySet(UserDTO userDTO, User user) {
if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) {
final String salt = KeyGenerators.string().generateKey();
user.setPasswordSalt(salt);
final String saltedPassword = salt + userDTO.getPassword();
user.setPasswordHash(passwordEncoder.encode(saltedPassword));
user.setPasswordHash(passwordEncoder.encode(userDTO.getPassword()));
}
}