Add: all of Huertos but controllers (jwt verification, MSR model, etc). Add: RS256 to global JWT handling

This commit is contained in:
Jose
2026-01-20 03:08:53 +01:00
parent eaeb0c4f4f
commit 21281b10cc
100 changed files with 3036 additions and 387 deletions

View File

@@ -1,5 +1,6 @@
package net.miarma.backend.core.config;
import net.miarma.backend.core.security.JwtFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@@ -37,15 +38,4 @@ public class SecurityConfig {
return http.build();
}
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}

View File

@@ -15,7 +15,7 @@ import jakarta.validation.Valid;
import net.miarma.backend.core.model.Credential;
import net.miarma.backend.core.service.AuthService;
import net.miarma.backend.core.service.CredentialService;
import net.miarma.backend.core.service.JwtService;
import net.miarma.backlib.security.JwtService;
import net.miarma.backlib.dto.ChangePasswordRequest;
import net.miarma.backlib.dto.LoginRequest;
import net.miarma.backlib.dto.LoginResponse;

View File

@@ -3,6 +3,8 @@ package net.miarma.backend.core.controller;
import java.util.List;
import java.util.UUID;
import net.miarma.backlib.dto.ChangeRoleRequest;
import net.miarma.backlib.dto.ChangeStatusRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -39,26 +41,54 @@ public class CredentialController {
return ResponseEntity.ok(credentialService.getByUserId(userId));
}
@GetMapping("/{credentialId}")
@GetMapping("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Credential> getById(@PathVariable("credentialId") UUID credentialId) {
public ResponseEntity<Credential> getById(@PathVariable("credential_id") UUID credentialId) {
return ResponseEntity.ok(credentialService.getById(credentialId));
}
@PutMapping("/{credentialId}")
@PutMapping("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Credential> update(
@PathVariable("credentialId") UUID credentialId,
@PathVariable("credential_id") UUID credentialId,
@RequestBody CredentialDto dto
) {
dto.setCredentialId(credentialId);
return ResponseEntity.ok(credentialService.update(credentialId, dto));
}
@DeleteMapping("/{credentialId}")
@DeleteMapping("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Void> delete(@PathVariable("credentialId") UUID credentialId) {
public ResponseEntity<Void> delete(@PathVariable("credential_id") UUID credentialId) {
credentialService.delete(credentialId);
return ResponseEntity.noContent().build();
}
@GetMapping("/{credential_id}/status")
public ResponseEntity<Byte> getStatus(@PathVariable("credential_id") UUID credentialId) {
return ResponseEntity.ok(credentialService.getStatus(credentialId));
}
@PutMapping("/{credential_id}/status")
public ResponseEntity<Void> updateStatus(
@PathVariable("credential_id") UUID credentialId,
@RequestBody ChangeStatusRequest req
) {
credentialService.updateStatus(credentialId, req.status());
return ResponseEntity.noContent().build();
}
@GetMapping("/{credential_id}/role")
public ResponseEntity<Byte> getRole(@PathVariable("credential_id") UUID credentialId) {
return ResponseEntity.ok(credentialService.getRole(credentialId));
}
@PutMapping("/{credential_id}/role")
public ResponseEntity<Void> updateRole(
@PathVariable("credential_id") UUID credentialId,
@RequestBody ChangeRoleRequest req
) {
credentialService.updateRole(credentialId, req.role());
return ResponseEntity.noContent().build();
}
}

View File

@@ -55,7 +55,7 @@ public class FileController {
@RequestParam String fileName,
@RequestParam String mimeType,
@RequestParam UUID uploadedBy,
@RequestParam Short context,
@RequestParam Byte context,
@RequestPart("file") MultipartFile file
) throws IOException {
File entity = new File();

View File

@@ -0,0 +1,24 @@
package net.miarma.backend.core.controller;
import net.miarma.backend.core.service.ScreenshotService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/screenshot")
public class ScreenshotController {
private final ScreenshotService screenshotService;
public ScreenshotController(ScreenshotService screenshotService) {
this.screenshotService = screenshotService;
}
@GetMapping("/screenshot")
public Mono<ResponseEntity<byte[]>> getScreenshot(@RequestParam String url) {
return screenshotService.getScreenshot(url);
}
}

View File

@@ -3,6 +3,10 @@ package net.miarma.backend.core.controller;
import java.util.List;
import java.util.UUID;
import net.miarma.backlib.dto.ChangeRoleRequest;
import net.miarma.backlib.dto.ChangeAvatarRequest;
import net.miarma.backlib.dto.ChangeStatusRequest;
import net.miarma.backlib.dto.UserExistsResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -16,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
import net.miarma.backend.core.mapper.UserMapper;
import net.miarma.backend.core.model.User;
import net.miarma.backend.core.service.JwtService;
import net.miarma.backlib.security.JwtService;
import net.miarma.backend.core.service.UserService;
import net.miarma.backlib.dto.UserDto;
@@ -59,6 +63,45 @@ public class UserController {
public ResponseEntity<String> getAvatar(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getById(userId).getAvatar());
}
@PutMapping("/{user_id}/avatar")
public ResponseEntity<UserDto> updateAvatar(@PathVariable("user_id") UUID userId, @RequestBody ChangeAvatarRequest avatar) {
return ResponseEntity.ok(userService.updateAvatar(userId, avatar));
}
@GetMapping("/{user_id}/status")
public ResponseEntity<Byte> getStatus(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getStatus(userId));
}
@PutMapping("/{user_id}/status")
public ResponseEntity<Void> updateStatus(
@PathVariable("user_id") UUID userId,
@RequestBody ChangeStatusRequest req
) {
userService.updateStatus(userId, req.status());
return ResponseEntity.noContent().build();
}
@GetMapping("/{user_id}/role")
public ResponseEntity<Byte> getRole(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getRole(userId));
}
@PutMapping("/{user_id}/role")
public ResponseEntity<Void> updateRole(
@PathVariable("user_id") UUID userid,
@RequestBody ChangeRoleRequest req
) {
userService.updateRole(userid, req.role());
return ResponseEntity.noContent().build();
}
@GetMapping("/{user_id}/exists")
public ResponseEntity<UserExistsResponse> exists(@PathVariable("user_id") UUID userId) {
boolean exists = userService.exists(userId);
return ResponseEntity.ok(new UserExistsResponse(exists));
}
@GetMapping("/me")
public ResponseEntity<UserDto> getMe(@RequestHeader("Authorization") String authHeader) {

View File

@@ -50,6 +50,7 @@ public class Credential {
private String email;
private String password;
private Byte status;
private Byte role;
@CreationTimestamp
private Instant createdAt;
@@ -142,6 +143,10 @@ public class Credential {
this.status = status;
}
public Byte getRole() { return role; }
public void setRole(Byte role) { this.role = role; }
public Instant getCreatedAt() {
return createdAt;
}

View File

@@ -43,7 +43,7 @@ public class File {
private Instant uploadedAt;
@Column(name = "context", nullable = false)
private Short context;
private Byte context;
@PrePersist
@PreUpdate
@@ -112,11 +112,11 @@ public class File {
return uploadedAt;
}
public Short getContext() {
public Byte getContext() {
return context;
}
public void setContext(Short context) {
public void setContext(Byte context) {
this.context = context;
}
}

View File

@@ -10,7 +10,7 @@ import net.miarma.backend.core.model.File;
public interface FileRepository extends JpaRepository<File, byte[]> {
List<File> findByUploadedBy(UUID uploadedBy);
List<File> findByContext(short context);
List<File> findByContext(Byte context);
List<File> findByUploadedByAndContext(UUID uploadedBy, short context);
List<File> findByUploadedByAndContext(UUID uploadedBy, Byte context);
}

View File

@@ -1,4 +1,4 @@
package net.miarma.backend.core.config;
package net.miarma.backend.core.security;
import java.io.IOException;
import java.util.List;
@@ -16,7 +16,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backend.core.model.User;
import net.miarma.backend.core.service.JwtService;
import net.miarma.backlib.security.JwtService;
import net.miarma.backend.core.service.UserService;
@Component

View File

@@ -20,11 +20,11 @@ public class AuthService {
private final CredentialService credentialService;
private final UserService userService;
private final JwtService jwtService;
private final net.miarma.backlib.security.JwtService jwtService;
private final PasswordEncoder passwordEncoder;
public AuthService(CredentialService credentialService, UserService userService,
JwtService jwtService, PasswordEncoder passwordEncoder) {
net.miarma.backlib.security.JwtService jwtService, PasswordEncoder passwordEncoder) {
this.credentialService = credentialService;
this.userService = userService;
this.jwtService = jwtService;
@@ -57,7 +57,7 @@ public class AuthService {
user = credentialService.getByEmail(request.getEmail());
} catch (RuntimeException e) {
UserDto dto = new UserDto();
dto.setUserId(UUID.randomUUID());
dto.userId(UUID.randomUUID());
dto.setDisplayName(request.getDisplayName());
user = userService.create(dto);
}

View File

@@ -127,11 +127,11 @@ public class CredentialService {
Credential cred = credentialRepository.findById(idBytes)
.orElseThrow(() -> new RuntimeException("Credential not found"));
if (!passwordEncoder.matches(request.getOldPassword(), cred.getPassword())) {
if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) {
throw new IllegalArgumentException("Old password is incorrect");
}
cred.setPassword(passwordEncoder.encode(request.getNewPassword()));
cred.setPassword(passwordEncoder.encode(request.newPassword()));
return credentialRepository.save(cred);
}
@@ -142,4 +142,30 @@ public class CredentialService {
credentialRepository.deleteById(idBytes);
}
public Byte getStatus(UUID credentialId) {
Credential credential = credentialRepository.findById(UuidUtil.uuidToBin(credentialId))
.orElseThrow(() -> new RuntimeException("User not found"));;
return credential.getStatus();
}
public void updateStatus(UUID credentialId, Byte status) {
Credential credential = credentialRepository.findById(UuidUtil.uuidToBin(credentialId))
.orElseThrow(() -> new RuntimeException("User not found"));;
credential.setStatus(status);
credentialRepository.save(credential);
}
public Byte getRole(UUID credentialId) {
Credential credential = credentialRepository.findById(UuidUtil.uuidToBin(credentialId))
.orElseThrow(() -> new RuntimeException("User not found"));;
return credential.getRole();
}
public void updateRole(UUID credentialId, Byte role) {
Credential credential = credentialRepository.findById(UuidUtil.uuidToBin(credentialId))
.orElseThrow(() -> new RuntimeException("User not found"));;
credential.setRole(role);
credentialRepository.save(credential);
}
}

View File

@@ -39,7 +39,7 @@ public class FileService {
return fileRepository.findByUploadedBy(userId);
}
if (params.containsKey("context")) {
short context = Short.parseShort(params.get("context"));
Byte context = Byte.parseByte(params.get("context"));
return fileRepository.findByContext(context);
}
return fileRepository.findAll();

View File

@@ -1,56 +0,0 @@
package net.miarma.backend.core.service;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.UUID;
@Service
public class JwtService {
private final String secret = "miarma-esto-es-un-secreto-super-largo-para-jwt-1234567890";
private final Key key = Keys.hmacShaKeyFor(secret.getBytes());
private final long expiration = 3600_000;
public String generateToken(UUID userId, Byte serviceId) {
Date now = new Date();
Date exp = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userId.toString())
.claim("service", serviceId)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public UUID getUserId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return UUID.fromString(claims.getSubject());
}
public Byte getServiceId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return ((Number) claims.get("service")).byteValue();
}
public Date getExpiration(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return claims.getExpiration();
}
}

View File

@@ -0,0 +1,34 @@
package net.miarma.backend.core.service;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Service
public class ScreenshotService {
private final WebClient webClient;
private final String URI = "http://screenshoter:7000";
public ScreenshotService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl(URI).build();
}
public Mono<ResponseEntity<byte[]>> getScreenshot(String url) {
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
return webClient.get()
.uri("/screenshot?url=" + encodedUrl)
.retrieve()
.toEntity(byte[].class)
.map(response -> ResponseEntity
.status(response.getStatusCode())
.header("Content-Type", "image/png")
.body(response.getBody())
);
}
}

View File

@@ -3,6 +3,8 @@ package net.miarma.backend.core.service;
import java.util.List;
import java.util.UUID;
import net.miarma.backend.core.mapper.UserMapper;
import net.miarma.backlib.dto.ChangeAvatarRequest;
import org.springframework.stereotype.Service;
import jakarta.transaction.Transactional;
@@ -75,4 +77,43 @@ public class UserService {
throw new RuntimeException("User not found");
userRepository.deleteById(idBytes);
}
public UserDto updateAvatar(UUID userId, ChangeAvatarRequest req) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new RuntimeException("User not found"));
user.setAvatar(req.avatar());
userRepository.save(user);
return UserMapper.toDto(user);
}
public Byte getStatus(UUID userId) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new RuntimeException("User not found"));;
return user.getGlobalStatus();
}
public void updateStatus(UUID userId, Byte status) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new RuntimeException("User not found"));;
user.setGlobalStatus(status);
userRepository.save(user);
}
public Byte getRole(UUID userId) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new RuntimeException("User not found"));;
return user.getGlobalRole();
}
public void updateRole(UUID userId, Byte role) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new RuntimeException("User not found"));;
user.setGlobalRole(role);
userRepository.save(user);
}
public boolean exists(UUID userId) {
return userRepository.existsById(UuidUtil.uuidToBin(userId));
}
}

View File

@@ -16,7 +16,7 @@ spring:
jpa:
open-in-view: false
hibernate:
ddl-auto: update
ddl-auto: validate
properties:
hibernate:
format_sql: true
@@ -35,6 +35,11 @@ logging:
org.hibernate.orm.jdbc.bind: TRACE
org.springframework.security: INFO
jwt:
private-key-path: /home/jomaa/.config/miarma-backend/private.pem
public-key-path: /home/jomaa/.config/miarma-backend/public.pem
expiration-ms: 3600000
management:
endpoints:
web: