Add: custom Exception for fine-grain error handling. Add: env variables for deploying in prod.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package net.miarma.backend.core.config;
|
||||
|
||||
import net.miarma.backend.core.security.JwtFilter;
|
||||
import net.miarma.backlib.http.RestAccessDeniedHandler;
|
||||
import net.miarma.backlib.http.RestAuthEntryPoint;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@@ -18,10 +20,18 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig {
|
||||
private final JwtFilter jwtFilter;
|
||||
private final JwtFilter jwtFilter;
|
||||
private final RestAuthEntryPoint authEntryPoint;
|
||||
private final RestAccessDeniedHandler accessDeniedHandler;
|
||||
|
||||
public SecurityConfig(JwtFilter jwtFilter) {
|
||||
public SecurityConfig(
|
||||
JwtFilter jwtFilter,
|
||||
RestAuthEntryPoint authEntryPoint,
|
||||
RestAccessDeniedHandler accessDeniedHandler
|
||||
) {
|
||||
this.jwtFilter = jwtFilter;
|
||||
this.authEntryPoint = authEntryPoint;
|
||||
this.accessDeniedHandler = accessDeniedHandler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -29,6 +39,10 @@ public class SecurityConfig {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(authEntryPoint)
|
||||
.accessDeniedHandler(accessDeniedHandler)
|
||||
)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/auth/**", "/screenshot").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
|
||||
@@ -2,6 +2,9 @@ package net.miarma.backend.core.service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import net.miarma.backlib.exception.ConflictException;
|
||||
import net.miarma.backlib.exception.ForbiddenException;
|
||||
import net.miarma.backlib.exception.UnauthorizedException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -34,9 +37,13 @@ public class AuthService {
|
||||
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
Credential cred = credentialService.getForLogin(request.serviceId(), request.username());
|
||||
|
||||
|
||||
if (!passwordEncoder.matches(request.password(), cred.getPassword())) {
|
||||
throw new RuntimeException("Invalid credentials");
|
||||
throw new UnauthorizedException("Invalid credentials");
|
||||
}
|
||||
|
||||
if (cred.getStatus() == 0) {
|
||||
throw new ForbiddenException("This account is inactive");
|
||||
}
|
||||
|
||||
String token = jwtService.generateToken(cred.getUserId(), request.serviceId());
|
||||
@@ -48,13 +55,13 @@ public class AuthService {
|
||||
|
||||
public LoginResponse register(RegisterRequest request) {
|
||||
if (credentialService.existsByUsernameAndService(request.username(), request.serviceId())) {
|
||||
throw new RuntimeException("Username already taken");
|
||||
throw new ConflictException("Username already taken");
|
||||
}
|
||||
|
||||
User user;
|
||||
try {
|
||||
user = credentialService.getByEmail(request.email());
|
||||
} catch (RuntimeException e) {
|
||||
} catch (Exception e) {
|
||||
UserDto dto = new UserDto();
|
||||
dto.setUserId(UUID.randomUUID());
|
||||
dto.setDisplayName(request.displayName());
|
||||
|
||||
@@ -3,6 +3,10 @@ package net.miarma.backend.core.service;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.miarma.backlib.exception.BadRequestException;
|
||||
import net.miarma.backlib.exception.ConflictException;
|
||||
import net.miarma.backlib.exception.NotFoundException;
|
||||
import net.miarma.backlib.exception.ValidationException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -29,30 +33,30 @@ public class CredentialService {
|
||||
public Credential getById(UUID credentialId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
|
||||
return credentialRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||
.orElseThrow(() -> new NotFoundException("Credential not found"));
|
||||
}
|
||||
|
||||
public Credential create(Credential credential) {
|
||||
if (credential.getUsername() == null || credential.getUsername().isBlank()) {
|
||||
throw new IllegalArgumentException("Username cannot be blank");
|
||||
throw new ValidationException("Username cannot be blank");
|
||||
}
|
||||
if (credential.getEmail() == null || !credential.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
|
||||
throw new IllegalArgumentException("Invalid email format");
|
||||
throw new ValidationException("Invalid email format");
|
||||
}
|
||||
if (credential.getPassword() == null || credential.getPassword().length() < 6) {
|
||||
throw new IllegalArgumentException("Password must be at least 6 characters");
|
||||
throw new ValidationException("Password must be at least 6 characters");
|
||||
}
|
||||
if (credential.getServiceId() == null || credential.getServiceId() < 0) {
|
||||
throw new IllegalArgumentException("ServiceId must be positive");
|
||||
throw new ValidationException("ServiceId must be positive");
|
||||
}
|
||||
|
||||
boolean existsUsername = credentialRepository.existsByUsernameAndServiceId(
|
||||
credential.getUsername(), credential.getServiceId());
|
||||
if (existsUsername) throw new IllegalArgumentException("Username already exists for this service");
|
||||
if (existsUsername) throw new ConflictException("Username already exists for this service");
|
||||
|
||||
boolean existsEmail = credentialRepository.existsByEmailAndServiceId(
|
||||
credential.getEmail(), credential.getServiceId());
|
||||
if (existsEmail) throw new IllegalArgumentException("Email already exists for this service");
|
||||
if (existsEmail) throw new ConflictException("Email already exists for this service");
|
||||
|
||||
credential.setCredentialId(UUID.randomUUID());
|
||||
credential.setPassword(passwordEncoder.encode(credential.getPassword()));
|
||||
@@ -74,25 +78,25 @@ public class CredentialService {
|
||||
public List<Credential> getByUserId(UUID userId) {
|
||||
List<Credential> creds = credentialRepository.findByUserId(UuidUtil.uuidToBin(userId));
|
||||
if (creds.isEmpty()) {
|
||||
throw new RuntimeException("User has no credentials");
|
||||
throw new NotFoundException("User has no credentials");
|
||||
}
|
||||
return creds;
|
||||
}
|
||||
|
||||
public User getByEmail(String email) {
|
||||
return credentialRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new RuntimeException("No credential found for email"))
|
||||
.orElseThrow(() -> new NotFoundException("No credential found for email"))
|
||||
.getUser();
|
||||
}
|
||||
|
||||
public Credential getByUserIdAndService(UUID userId, Byte serviceId) {
|
||||
return credentialRepository.findByUserIdAndServiceId(userId, serviceId)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found in this site"));
|
||||
.orElseThrow(() -> new NotFoundException("Credential not found in this site"));
|
||||
}
|
||||
|
||||
public Credential getForLogin(Byte serviceId, String username) {
|
||||
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
||||
.orElseThrow(() -> new RuntimeException("Invalid credentials"));
|
||||
.orElseThrow(() -> new BadRequestException("Invalid credentials"));
|
||||
}
|
||||
|
||||
public boolean existsByUsernameAndService(String username, int serviceId) {
|
||||
@@ -102,7 +106,7 @@ public class CredentialService {
|
||||
public boolean isOwner(UUID credentialId, UUID userId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
|
||||
Credential c = credentialRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||
.orElseThrow(() -> new NotFoundException("Credential not found"));
|
||||
return c.getUserId().equals(userId);
|
||||
}
|
||||
|
||||
@@ -110,16 +114,16 @@ public class CredentialService {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
|
||||
|
||||
Credential cred = credentialRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||
.orElseThrow(() -> new NotFoundException("Credential not found"));
|
||||
|
||||
if (dto.getUsername() != null && dto.getUsername().isBlank()) {
|
||||
throw new IllegalArgumentException("Username cannot be blank");
|
||||
throw new ValidationException("Username cannot be blank");
|
||||
}
|
||||
if (dto.getEmail() != null && !dto.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
|
||||
throw new IllegalArgumentException("Invalid email format");
|
||||
throw new ValidationException("Invalid email format");
|
||||
}
|
||||
if (dto.getServiceId() != null && dto.getServiceId() < 0) {
|
||||
throw new IllegalArgumentException("ServiceId must be positive");
|
||||
throw new ValidationException("ServiceId must be positive");
|
||||
}
|
||||
|
||||
if (dto.getUsername() != null) cred.setUsername(dto.getUsername());
|
||||
@@ -134,10 +138,10 @@ public class CredentialService {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
|
||||
|
||||
Credential cred = credentialRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||
.orElseThrow(() -> new NotFoundException("Credential not found"));
|
||||
|
||||
if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) {
|
||||
throw new IllegalArgumentException("Old password is incorrect");
|
||||
throw new ValidationException("Old password is incorrect");
|
||||
}
|
||||
|
||||
cred.setPassword(passwordEncoder.encode(request.newPassword()));
|
||||
@@ -147,19 +151,19 @@ public class CredentialService {
|
||||
public void delete(UUID credentialId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
|
||||
if(!credentialRepository.existsById(idBytes))
|
||||
throw new RuntimeException("Credential not found");
|
||||
throw new NotFoundException("Credential not found");
|
||||
credentialRepository.deleteById(idBytes);
|
||||
}
|
||||
|
||||
public Byte getStatus(UUID credentialId) {
|
||||
Credential credential = credentialRepository.findById(UuidUtil.uuidToBin(credentialId))
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));;
|
||||
.orElseThrow(() -> new NotFoundException("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"));;
|
||||
.orElseThrow(() -> new NotFoundException("User not found"));;
|
||||
credential.setStatus(status);
|
||||
credentialRepository.save(credential);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,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.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class FileService {
|
||||
public File getById(UUID fileId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(fileId);
|
||||
return fileRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("File not found"));
|
||||
.orElseThrow(() -> new NotFoundException("File not found"));
|
||||
}
|
||||
|
||||
public List<File> getAll() {
|
||||
@@ -62,7 +63,7 @@ public class FileService {
|
||||
public File update(File file) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(file.getFileId());
|
||||
if (!fileRepository.existsById(idBytes)) {
|
||||
throw new RuntimeException("File not found");
|
||||
throw new NotFoundException("File not found");
|
||||
}
|
||||
return fileRepository.save(file);
|
||||
}
|
||||
@@ -70,7 +71,7 @@ public class FileService {
|
||||
public void delete(UUID fileId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(fileId);
|
||||
if (!fileRepository.existsById(idBytes)) {
|
||||
throw new RuntimeException("File not found");
|
||||
throw new NotFoundException("File not found");
|
||||
}
|
||||
fileRepository.deleteById(idBytes);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.UUID;
|
||||
|
||||
import net.miarma.backend.core.mapper.UserMapper;
|
||||
import net.miarma.backlib.dto.ChangeAvatarRequest;
|
||||
import net.miarma.backlib.exception.NotFoundException;
|
||||
import net.miarma.backlib.exception.ValidationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
@@ -29,12 +31,12 @@ public class UserService {
|
||||
public User getById(UUID userId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(userId);
|
||||
return userRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
.orElseThrow(() -> new NotFoundException("User not found"));
|
||||
}
|
||||
|
||||
public User create(UserDto dto) {
|
||||
if(dto.getDisplayName() == null || dto.getDisplayName().isBlank()) {
|
||||
throw new RuntimeException("Display name is required");
|
||||
throw new ValidationException("Display name is required");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
@@ -50,7 +52,7 @@ public class UserService {
|
||||
public User update(UUID userId, UserDto dto) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(userId);
|
||||
User user = userRepository.findById(idBytes)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
.orElseThrow(() -> new NotFoundException("User not found"));
|
||||
|
||||
if (dto.getDisplayName() != null) {
|
||||
String displayName = dto.getDisplayName().trim();
|
||||
@@ -74,13 +76,13 @@ public class UserService {
|
||||
public void delete(UUID userId) {
|
||||
byte[] idBytes = UuidUtil.uuidToBin(userId);
|
||||
if(!userRepository.existsById(idBytes))
|
||||
throw new RuntimeException("User not found");
|
||||
throw new NotFoundException("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"));
|
||||
.orElseThrow(() -> new NotFoundException("User not found"));
|
||||
user.setAvatar(req.avatar());
|
||||
userRepository.save(user);
|
||||
return UserMapper.toDto(user);
|
||||
@@ -88,26 +90,26 @@ public class UserService {
|
||||
|
||||
public Byte getStatus(UUID userId) {
|
||||
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));;
|
||||
.orElseThrow(() -> new NotFoundException("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"));;
|
||||
.orElseThrow(() -> new NotFoundException("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"));;
|
||||
.orElseThrow(() -> new NotFoundException("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"));;
|
||||
.orElseThrow(() -> new NotFoundException("User not found"));;
|
||||
user.setGlobalRole(role);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
21
core/src/main/resources/application-dev.yml
Normal file
21
core/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /v2/core
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mariadb://localhost:3306/miarma_v2
|
||||
username: admin
|
||||
password: ${DB_PASS}
|
||||
driver-class-name: org.mariadb.jdbc.Driver
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.orm.jdbc.bind: TRACE
|
||||
org.springframework.security: DEBUG
|
||||
|
||||
jwt:
|
||||
private-key-path: /home/jomaa/.config/miarma-backend/private.pem
|
||||
public-key-path: /home/jomaa/.config/miarma-backend/public.pem
|
||||
20
core/src/main/resources/application-prod.yml
Normal file
20
core/src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /v2/core
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mariadb://mariadb:3306/miarma_v2
|
||||
username: ${DB_USER}
|
||||
password: ${DB_PASS}
|
||||
driver-class-name: org.mariadb.jdbc.Driver
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: INFO
|
||||
org.hibernate.SQL: WARN
|
||||
|
||||
jwt:
|
||||
private-key-path: ${JWT_PRIVATE_KEY}
|
||||
public-key-path: ${JWT_PUBLIC_KEY}
|
||||
@@ -1,43 +1,21 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /v2/core
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: core-service
|
||||
|
||||
datasource:
|
||||
url: jdbc:mariadb://localhost:3306/miarma_v2
|
||||
username: admin
|
||||
password: ositovito
|
||||
driver-class-name: org.mariadb.jdbc.Driver
|
||||
|
||||
jpa:
|
||||
open-in-view: false
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
jdbc:
|
||||
time_zone: UTC
|
||||
|
||||
jackson:
|
||||
serialization:
|
||||
indent-output: true
|
||||
default-property-inclusion: non_null
|
||||
time-zone: Europe/Madrid
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user