backend rewritten in rust #11

Merged
mixel merged 9 commits from new-backend into main 2024-10-04 17:28:26 +02:00
7 changed files with 146 additions and 46 deletions
Showing only changes of commit c37139287f - Show all commits

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));
} }
} }