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