Add: missing create methods in controllers. Fix: SYSTEM token gets the refresh now. Add: CORS. Add: HTTP to core.

This commit is contained in:
Jose
2026-01-25 23:42:50 +01:00
parent 2d255a7f0b
commit e4461f7790
47 changed files with 709 additions and 198 deletions

View File

@@ -83,7 +83,7 @@
<dependency>
<groupId>net.miarma</groupId>
<artifactId>backlib</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,28 @@
package net.miarma.backend.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:3000",
"http://localhost:8081",
"http://huertos:8081"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}

View File

@@ -6,6 +6,7 @@ import net.miarma.backlib.http.RestAuthEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -15,6 +16,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
@@ -34,9 +40,23 @@ public class SecurityConfig {
this.accessDeniedHandler = accessDeniedHandler;
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(ex -> ex

View File

@@ -1,15 +1,13 @@
package net.miarma.backend.core.controller;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import net.miarma.backend.core.model.Credential;
@@ -106,4 +104,10 @@ public class AuthController {
return ResponseEntity.ok(Map.of("message", "Password changed successfully"));
}
@GetMapping("/validate")
public ResponseEntity<Boolean> validate(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.substring(7);
return ResponseEntity.ok(jwtService.validateToken(token));
}
}

View File

@@ -72,7 +72,7 @@ public class FileController {
@PutMapping("/{fileId}")
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
public ResponseEntity<File> update(@PathVariable("file_id") UUID fileId, @RequestBody File file) {
public ResponseEntity<File> update(@PathVariable("fileId") UUID fileId, @RequestBody File file) {
file.setFileId(fileId);
File updated = fileService.update(file);
return ResponseEntity.ok(updated);
@@ -80,7 +80,7 @@ public class FileController {
@DeleteMapping("/{fileId}")
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
public ResponseEntity<Void> delete(@PathVariable("file_id") UUID fileId, @RequestBody Map<String,String> body) throws IOException {
public ResponseEntity<Void> delete(@PathVariable("fileId") UUID fileId, @RequestBody Map<String,String> body) throws IOException {
String filePath = body.get("file_path");
Files.deleteIfExists(Paths.get(filePath));
fileService.delete(fileId);

View File

@@ -28,7 +28,8 @@ public interface CredentialRepository extends JpaRepository<Credential, byte[]>
Optional<Credential> findByServiceIdAndEmail(Byte serviceId, String email);
Optional<Credential> findByUserIdAndServiceId(UUID userId, Byte serviceId);
@Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin AND c.serviceId = :serviceId")
Optional<Credential> findByUserIdAndServiceId(@Param("userIdBin") byte[] userIdBin, @Param("serviceId") Byte serviceId);
Optional<Credential> findByUsernameAndServiceId(String username, int serviceId);

View File

@@ -38,16 +38,16 @@ public class CredentialService {
public Credential create(Credential credential) {
if (credential.getUsername() == null || credential.getUsername().isBlank()) {
throw new ValidationException("Username cannot be blank");
throw new ValidationException("userName", "Username cannot be blank");
}
if (credential.getEmail() == null || !credential.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
throw new ValidationException("Invalid email format");
throw new ValidationException("email", "Invalid email format");
}
if (credential.getPassword() == null || credential.getPassword().length() < 6) {
throw new ValidationException("Password must be at least 6 characters");
throw new ValidationException("password", "Password must be at least 6 characters");
}
if (credential.getServiceId() == null || credential.getServiceId() < 0) {
throw new ValidationException("ServiceId must be positive");
throw new ValidationException("serviceId", "ServiceId must be positive");
}
boolean existsUsername = credentialRepository.existsByUsernameAndServiceId(
@@ -90,7 +90,7 @@ public class CredentialService {
}
public Credential getByUserIdAndService(UUID userId, Byte serviceId) {
return credentialRepository.findByUserIdAndServiceId(userId, serviceId)
return credentialRepository.findByUserIdAndServiceId(UuidUtil.uuidToBin(userId), serviceId)
.orElseThrow(() -> new NotFoundException("Credential not found in this site"));
}
@@ -117,13 +117,13 @@ public class CredentialService {
.orElseThrow(() -> new NotFoundException("Credential not found"));
if (dto.getUsername() != null && dto.getUsername().isBlank()) {
throw new ValidationException("Username cannot be blank");
throw new ValidationException("userName", "Username cannot be blank");
}
if (dto.getEmail() != null && !dto.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
throw new ValidationException("Invalid email format");
throw new ValidationException("email", "Invalid email format");
}
if (dto.getServiceId() != null && dto.getServiceId() < 0) {
throw new ValidationException("ServiceId must be positive");
throw new ValidationException("serviceId", "ServiceId must be positive");
}
if (dto.getUsername() != null) cred.setUsername(dto.getUsername());
@@ -141,7 +141,7 @@ public class CredentialService {
.orElseThrow(() -> new NotFoundException("Credential not found"));
if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) {
throw new ValidationException("Old password is incorrect");
throw new ValidationException("oldPassword", "Old password is incorrect");
}
cred.setPassword(passwordEncoder.encode(request.newPassword()));

View File

@@ -12,6 +12,7 @@ import java.util.UUID;
import net.miarma.backend.core.mapper.FileMapper;
import net.miarma.backlib.dto.FileDto;
import net.miarma.backlib.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -24,7 +25,9 @@ import net.miarma.backlib.util.UuidUtil;
public class FileService {
private final FileRepository fileRepository;
private final String basePath = "/var/www/files";
@Value("${filesDir}")
private String filesDir;
public FileService(FileRepository fileRepository) {
this.fileRepository = fileRepository;
@@ -45,7 +48,7 @@ public class FileService {
}
public File create(FileDto.Request dto, byte[] fileBinary) throws IOException {
Path dirPath = Paths.get(basePath, String.valueOf(dto.getContext()));
Path dirPath = Paths.get(filesDir, String.valueOf(dto.getContext()));
if (!Files.exists(dirPath)) {
Files.createDirectories(dirPath);
}

View File

@@ -36,7 +36,7 @@ public class UserService {
public User create(UserDto dto) {
if(dto.getDisplayName() == null || dto.getDisplayName().isBlank()) {
throw new ValidationException("Display name is required");
throw new ValidationException("displayName", "Display name is required");
}
User user = new User();

View File

@@ -16,6 +16,8 @@ logging:
org.hibernate.orm.jdbc.bind: TRACE
org.springframework.security: DEBUG
filesDir: "/home/jomaa/.config/miarma-backend/files"
jwt:
private-key-path: /home/jomaa/.config/miarma-backend/private.pem
public-key-path: /home/jomaa/.config/miarma-backend/public.pem

View File

@@ -15,6 +15,8 @@ logging:
org.springframework.security: INFO
org.hibernate.SQL: WARN
filesDir: "/files"
jwt:
private-key-path: ${JWT_PRIVATE_KEY}
public-key-path: ${JWT_PUBLIC_KEY}