Restored dev

This commit is contained in:
Jose
2026-02-15 06:26:40 +01:00
parent de98ed5cc5
commit 3796e6f190
191 changed files with 7581 additions and 883 deletions

9
backlib/backlib.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

91
backlib/pom.xml Normal file
View File

@@ -0,0 +1,91 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>backlib</artifactId>
<groupId>net.miarma</groupId>
<version>1.1.1</version>
<properties>
<java.version>25</java.version>
<spring.boot.version>4.0.1</spring.boot.version>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
<snapshotRepository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,29 @@
package net.miarma.backlib.config;
import net.miarma.backlib.security.JwtService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityCommonConfig {
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public JwtService jwtService() {
return new JwtService();
}
}

View File

@@ -0,0 +1,65 @@
package net.miarma.backlib.dto;
import tools.jackson.databind.ObjectMapper;
import java.time.Instant;
public class ApiErrorDto {
private int status;
private String error;
private String message;
private String path;
private Instant timestamp;
public ApiErrorDto(int status, String error, String message, String path) {
this.status = status;
this.error = error;
this.message = message;
this.path = path;
this.timestamp = Instant.now();
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}

View File

@@ -0,0 +1,57 @@
package net.miarma.backlib.dto;
import tools.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.List;
import java.util.Map;
public class ApiValidationErrorDto {
private int status;
private Map<String,String> errors;
private String path;
private Instant timestamp;
public ApiValidationErrorDto(Map<String,String> errors, String path) {
this.status = 422;
this.errors = errors;
this.path = path;
this.timestamp = Instant.now();
}
public Map<String,String> getErrors() {
return errors;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public void setErrors(Map<String,String> errors) {
this.errors = errors;
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record ChangeAvatarRequest(String avatar) {}

View File

@@ -0,0 +1,10 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record ChangePasswordRequest(@NotBlank String oldPassword,
@NotBlank String newPassword,
@NotBlank Byte serviceId) {}

View File

@@ -0,0 +1,4 @@
package net.miarma.backlib.dto;
public record ChangeRoleRequest(Byte role) {
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record ChangeStatusRequest(Byte status) {}

View File

@@ -0,0 +1,56 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class CreateCredentialDto {
private UUID userId;
private Byte serviceId;
private String username;
private String email;
private String password;
private Byte status;
public CreateCredentialDto() {}
public CreateCredentialDto(UUID userId, Byte serviceId, String username, String email,
String password, Byte status) {
this.userId = userId;
this.serviceId = serviceId;
this.username = username;
this.email = email;
this.password = password;
this.status = status;
}
// Getters y setters
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public Byte getServiceId() {
return serviceId;
}
public void setServiceId(Byte serviceId) {
this.serviceId = serviceId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Byte getStatus() {
return status;
}
public void setStatus(Byte status) {
this.status = status;
}
}

View File

@@ -0,0 +1,6 @@
package net.miarma.backlib.dto;
public record CreateUserDto(String displayName,
String avatar) {
}

View File

@@ -0,0 +1,79 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class CredentialDto {
private UUID credentialId;
private UUID userId;
private Byte serviceId;
private String username;
private String email;
private Byte status;
private Instant createdAt;
private Instant updatedAt;
public CredentialDto() {}
public CredentialDto(UUID credentialId, UUID userId, Byte serviceId, String username, String email,
Byte status, Instant createdAt, Instant updatedAt) {
this.credentialId = credentialId;
this.userId = userId;
this.serviceId = serviceId;
this.username = username;
this.email = email;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
// Getters y setters
public UUID getCredentialId() {
return credentialId;
}
public void setCredentialId(UUID credentialId) {
this.credentialId = credentialId;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public Byte getServiceId() {
return serviceId;
}
public void setServiceId(Byte serviceId) {
this.serviceId = serviceId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Byte getStatus() {
return status;
}
public void setStatus(Byte status) {
this.status = status;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,123 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class FileDto {
private FileDto() {}
public static class Request {
private String fileName;
private String filePath;
private String mimeType;
private Byte context;
private UUID uploadedBy;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Byte getContext() {
return context;
}
public void setContext(Byte context) {
this.context = context;
}
public UUID getUploadedBy() {
return uploadedBy;
}
public void setUploadedBy(UUID uploadedBy) {
this.uploadedBy = uploadedBy;
}
}
public static class Response {
private UUID fileId;
private String fileName;
private String filePath;
private String mimeType;
private UUID uploadedBy;
private Instant uploadedAt;
private Byte context;
public UUID getFileId() {
return fileId;
}
public void setFileId(UUID fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public UUID getUploadedBy() {
return uploadedBy;
}
public void setUploadedBy(UUID uploadedBy) {
this.uploadedBy = uploadedBy;
}
public Instant getUploadedAt() {
return uploadedAt;
}
public void setUploadedAt(Instant uploadedAt) {
this.uploadedAt = uploadedAt;
}
public Byte getContext() {
return context;
}
public void setContext(Byte context) {
this.context = context;
}
}
}

View File

@@ -0,0 +1,8 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.NotBlank;
import tools.jackson.databind.JsonNode;
public record LoginRequest(@NotBlank String username,
@NotBlank String password,
Byte serviceId) {}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public record LoginResponse(@JsonProperty("token") String token,
UserDto user,
CredentialDto account) {}

View File

@@ -0,0 +1,9 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.NotBlank;
public record RegisterRequest(String displayName,
@NotBlank String username,
@NotBlank String email,
@NotBlank String password,
Byte serviceId) {}

View File

@@ -0,0 +1,71 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class UserDto {
private UUID userId;
private String displayName;
private String avatar;
private Byte globalStatus;
private Byte globalRole;
private Instant createdAt;
private Instant updatedAt;
public UserDto() {}
public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole,
Instant createdAt, Instant updatedAt) {
this.userId = userId;
this.displayName = displayName;
this.avatar = avatar;
this.globalStatus = globalStatus;
this.globalRole = globalRole;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
// Getters y setters
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Byte getGlobalStatus() {
return globalStatus;
}
public void setGlobalStatus(Byte globalStatus) {
this.globalStatus = globalStatus;
}
public Byte getGlobalRole() {
return globalRole;
}
public void setGlobalRole(Byte globalRole) {
this.globalRole = globalRole;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,4 @@
package net.miarma.backlib.dto;
public record UserExistsResponse(boolean exists) {
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record UserWithCredentialDto(UserDto user, CredentialDto account) {}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class ConflictException extends RuntimeException {
public ConflictException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,5 @@
package net.miarma.backlib.exception;
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) { super(message); }
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,21 @@
package net.miarma.backlib.exception;
public class ValidationException extends RuntimeException {
private final String field;
private final String message;
public ValidationException(String field, String message) {
super(message);
this.field = field;
this.message = message;
}
public String getField() {
return field;
}
@Override
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,41 @@
package net.miarma.backlib.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
long start = System.currentTimeMillis();
filterChain.doFilter(request, response);
long duration = System.currentTimeMillis() - start;
log.info("({}) {} {} -> {} ({} ms)",
request.getRemoteAddr(),
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration
);
}
}

View File

@@ -0,0 +1,27 @@
package net.miarma.backlib.http;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@Profile("dev") // esto asegura que solo se cargue en dev
public class DevCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000") // tu frontend React
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.allowedHeaders("*");
}
};
}
}

View File

@@ -0,0 +1,106 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import net.miarma.backlib.dto.ApiErrorDto;
import net.miarma.backlib.dto.ApiValidationErrorDto;
import net.miarma.backlib.exception.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ApiErrorDto> handleNotFound(
NotFoundException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.NOT_FOUND.value(),
HttpStatus.NOT_FOUND.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ApiErrorDto> handleBadRequest(
BadRequestException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ApiErrorDto> handleUnauthorized(
UnauthorizedException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
}
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ApiErrorDto> handleForbidden(
ForbiddenException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
@ExceptionHandler(ConflictException.class)
public ResponseEntity<ApiErrorDto> handleConflict(
ConflictException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.CONFLICT.value(),
HttpStatus.CONFLICT.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiValidationErrorDto> handleValidation(
ValidationException ex, HttpServletRequest req) {
Map<String, String> errors = Map.of(ex.getField(), ex.getMessage());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_CONTENT).body(new ApiValidationErrorDto(errors, req.getRequestURI()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiErrorDto> handleAll(
Exception ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
"Internal server error",
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}

View File

@@ -0,0 +1,31 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backlib.dto.ApiErrorDto;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
"Forbidden",
request.getRequestURI()
);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json");
response.getWriter().write(error.toJson());
}
}

View File

@@ -0,0 +1,34 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backlib.dto.ApiErrorDto;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase(),
"Unauthorized",
request.getRequestURI()
);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write(error.toJson());
}
}

View File

@@ -0,0 +1,24 @@
package net.miarma.backlib.security;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
public class CoreAuthTokenHolder {
private volatile String token;
private volatile Instant expiresAt;
public String getToken() {
return token;
}
public boolean isExpired() {
return expiresAt == null || Instant.now().isAfter(expiresAt.minusSeconds(30));
}
public void setToken(String token, Instant expiresAt) {
this.token = token;
this.expiresAt = expiresAt;
}
}

View File

@@ -0,0 +1,102 @@
package net.miarma.backlib.security;
import io.jsonwebtoken.*;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class JwtService {
@Value("${jwt.private-key-path}")
private String privateKeyPath;
@Value("${jwt.public-key-path}")
private String publicKeyPath;
@Value("${jwt.expiration-ms}")
private long expiration;
private PrivateKey privateKey;
private PublicKey publicKey;
@PostConstruct
public void init() throws Exception {
this.privateKey = loadPrivateKey(privateKeyPath);
this.publicKey = loadPublicKey(publicKeyPath);
}
private PrivateKey loadPrivateKey(String path) throws Exception {
String pem = Files.readString(Path.of(path));
pem = pem.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(pem);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
private PublicKey loadPublicKey(String path) throws Exception {
String pem = Files.readString(Path.of(path));
pem = pem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(pem);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
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(privateKey, SignatureAlgorithm.RS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public UUID getUserId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return UUID.fromString(claims.getSubject());
}
public Byte getServiceId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return ((Number) claims.get("service")).byteValue();
}
public Date getExpiration(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return claims.getExpiration();
}
}

View File

@@ -0,0 +1,45 @@
package net.miarma.backlib.security;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PasswordGenerator {
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
private static final String DIGITS = "0123456789";
private static final String SYMBOLS = "!@#$%^&*"; // compatibles con bcrypt
private static final String ALL = UPPER + LOWER + DIGITS + SYMBOLS;
private static final SecureRandom random = new SecureRandom();
public static String generate(int length) {
if (length < 8) length = 8;
List<Character> password = new ArrayList<>();
password.add(getRandChar(UPPER));
password.add(getRandChar(LOWER));
password.add(getRandChar(DIGITS));
password.add(getRandChar(SYMBOLS));
while (password.size() < length) {
password.add(getRandChar(ALL));
}
Collections.shuffle(password, random);
StringBuilder sb = new StringBuilder();
for (char c : password) {
sb.append(c);
}
return sb.toString();
}
private static char getRandChar(String chars) {
return chars.charAt(random.nextInt(chars.length()));
}
}

View File

@@ -0,0 +1,45 @@
package net.miarma.backlib.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class ServiceAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public ServiceAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
if (path.startsWith("/users/service")) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String token = authHeader.substring(7);
if (!jwtService.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,21 @@
package net.miarma.backlib.util;
import java.nio.ByteBuffer;
import java.util.UUID;
public class UuidUtil {
public static byte[] uuidToBin(UUID uuid) {
ByteBuffer bb = ByteBuffer.allocate(16);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
public static UUID binToUUID(byte[] bin) {
ByteBuffer bb = ByteBuffer.wrap(bin);
long high = bb.getLong();
long low = bb.getLong();
return new UUID(high, low);
}
}

View File

@@ -0,0 +1,3 @@
artifactId=backlib
groupId=net.miarma
version=1.1.0

View File

@@ -0,0 +1,36 @@
net/miarma/backlib/dto/LoginResponse.class
net/miarma/backlib/dto/CreateUserDto.class
net/miarma/backlib/http/DevCorsConfig$1.class
net/miarma/backlib/dto/ApiErrorDto.class
net/miarma/backlib/dto/ChangeStatusRequest.class
net/miarma/backlib/exception/ConflictException.class
net/miarma/backlib/http/RestAuthEntryPoint.class
net/miarma/backlib/exception/NotFoundException.class
net/miarma/backlib/http/RestAccessDeniedHandler.class
net/miarma/backlib/dto/ChangeAvatarRequest.class
net/miarma/backlib/dto/FileDto$Response.class
net/miarma/backlib/dto/CredentialDto.class
net/miarma/backlib/util/UuidUtil.class
net/miarma/backlib/dto/FileDto$Request.class
net/miarma/backlib/dto/ApiValidationErrorDto.class
net/miarma/backlib/security/JwtService.class
net/miarma/backlib/dto/UserDto.class
net/miarma/backlib/dto/LoginRequest.class
net/miarma/backlib/exception/BadRequestException.class
net/miarma/backlib/security/CoreAuthTokenHolder.class
net/miarma/backlib/dto/CreateCredentialDto.class
net/miarma/backlib/dto/UserWithCredentialDto.class
net/miarma/backlib/dto/RegisterRequest.class
net/miarma/backlib/dto/ChangePasswordRequest.class
net/miarma/backlib/http/GlobalExceptionHandler.class
net/miarma/backlib/dto/UserExistsResponse.class
net/miarma/backlib/dto/ChangeRoleRequest.class
net/miarma/backlib/exception/ForbiddenException.class
net/miarma/backlib/exception/UnauthorizedException.class
net/miarma/backlib/dto/FileDto.class
net/miarma/backlib/security/PasswordGenerator.class
net/miarma/backlib/config/SecurityCommonConfig.class
net/miarma/backlib/filter/RequestLoggingFilter.class
net/miarma/backlib/exception/ValidationException.class
net/miarma/backlib/security/ServiceAuthFilter.class
net/miarma/backlib/http/DevCorsConfig.class

View File

@@ -0,0 +1,33 @@
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ApiValidationErrorDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CreateUserDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/filter/RequestLoggingFilter.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/DevCorsConfig.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/JwtService.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/PasswordGenerator.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java