Harden startup config

This commit is contained in:
2026-04-11 23:10:18 -06:00
parent 299462d231
commit 79b4f7a3e8
8 changed files with 36 additions and 28 deletions

View File

@@ -1,7 +1,5 @@
package com.petshop.backend.config; package com.petshop.backend.config;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.security.AppPrincipal;
import com.petshop.backend.service.ActivityLogService; import com.petshop.backend.service.ActivityLogService;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
@@ -18,15 +16,13 @@ import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
@Component @Component
@Order(Ordered.LOWEST_PRECEDENCE - 20) @Order(Ordered.LOWEST_PRECEDENCE - 20)
public class ActivityLoggingFilter extends OncePerRequestFilter { public class ActivityLoggingFilter extends OncePerRequestFilter {
private final UserRepository userRepository;
private final ActivityLogService activityLogService; private final ActivityLogService activityLogService;
public ActivityLoggingFilter(UserRepository userRepository, ActivityLogService activityLogService) { public ActivityLoggingFilter(ActivityLogService activityLogService) {
this.userRepository = userRepository;
this.activityLogService = activityLogService; this.activityLogService = activityLogService;
} }
@@ -73,12 +69,7 @@ public class ActivityLoggingFilter extends OncePerRequestFilter {
return; return;
} }
User user = userRepository.findById(userId).orElse(null);
if (user == null) {
return;
}
String activity = String.format("%s %s -> %d", request.getMethod(), request.getRequestURI(), response.getStatus()); String activity = String.format("%s %s -> %d", request.getMethod(), request.getRequestURI(), response.getStatus());
activityLogService.record(user, activity); activityLogService.record(userId, activity);
} }
} }

View File

@@ -1,7 +1,6 @@
package com.petshop.backend.config; package com.petshop.backend.config;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationVersion;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
@@ -38,10 +37,7 @@ public class FlywayContextInitializer implements ApplicationContextInitializer<C
Flyway flyway = Flyway.configure() Flyway flyway = Flyway.configure()
.dataSource(url, username, password) .dataSource(url, username, password)
.locations(locations) .locations(locations)
.baselineOnMigrate(environment.getProperty("spring.flyway.baseline-on-migrate", Boolean.class, false))
.baselineVersion(MigrationVersion.fromVersion(environment.getProperty("spring.flyway.baseline-version", "1")))
.load(); .load();
flyway.repair();
flyway.migrate(); flyway.migrate();
return; return;
} catch (RuntimeException ex) { } catch (RuntimeException ex) {

View File

@@ -101,7 +101,7 @@ public class AuthController {
User savedUser = userRepository.save(user); User savedUser = userRepository.save(user);
String token = jwtUtil.generateToken(savedUser); String token = jwtUtil.generateToken(savedUser);
activityLogService.record(savedUser, "POST /api/v1/auth/register -> 201"); activityLogService.record(savedUser.getId(), "POST /api/v1/auth/register -> 201");
return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse( return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse(
savedUser.getId(), savedUser.getId(),
@@ -124,7 +124,7 @@ public class AuthController {
.orElseThrow(() -> new UsernameNotFoundException("User not found")); .orElseThrow(() -> new UsernameNotFoundException("User not found"));
String token = jwtUtil.generateToken(user); String token = jwtUtil.generateToken(user);
activityLogService.record(user, "POST /api/v1/auth/login -> 200"); activityLogService.record(user.getId(), "POST /api/v1/auth/login -> 200");
return ResponseEntity.ok(new LoginResponse( return ResponseEntity.ok(new LoginResponse(
token, token,

View File

@@ -4,6 +4,7 @@ import com.petshop.backend.entity.User;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -23,6 +24,16 @@ public class JwtUtil {
@Value("${jwt.expiration}") @Value("${jwt.expiration}")
private Long expiration; private Long expiration;
@PostConstruct
void validateConfiguration() {
if (secret == null || secret.isBlank()) {
throw new IllegalStateException("JWT_SECRET must be configured");
}
if (secret.getBytes(StandardCharsets.UTF_8).length < 32) {
throw new IllegalStateException("JWT_SECRET must be at least 32 bytes long");
}
}
private SecretKey getSigningKey() { private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }

View File

@@ -28,13 +28,16 @@ public class ActivityLogService {
} }
@Transactional @Transactional
public void record(User user, String activity) { public void record(Long userId, String activity) {
if (user == null || activity == null || activity.isBlank()) { if (userId == null || activity == null || activity.isBlank()) {
return; return;
} }
try { try {
User managedUser = userRepository.findById(user.getId()).orElse(user); User managedUser = userRepository.findById(userId).orElse(null);
if (managedUser == null) {
return;
}
StoreLocation store = managedUser.getPrimaryStore(); StoreLocation store = managedUser.getPrimaryStore();
ActivityLog entry = new ActivityLog(); ActivityLog entry = new ActivityLog();
entry.setUser(managedUser); entry.setUser(managedUser);
@@ -50,6 +53,13 @@ public class ActivityLogService {
} }
} }
public void record(User user, String activity) {
if (user == null) {
return;
}
record(user.getId(), activity);
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<ActivityLogResponse> getLogs(int limit) { public List<ActivityLogResponse> getLogs(int limit) {
return activityLogRepository.findRecent(PageRequest.of(0, limit)).stream().map(this::toResponse).toList(); return activityLogRepository.findRecent(PageRequest.of(0, limit)).stream().map(this::toResponse).toList();

View File

@@ -0,0 +1,2 @@
jwt:
secret: ${JWT_SECRET:local-development-jwt-secret-change-me-please-123456}

View File

@@ -33,9 +33,7 @@ spring:
open-in-view: false open-in-view: false
flyway: flyway:
enabled: true enabled: false
baseline-on-migrate: true
baseline-version: 1
server: server:
port: ${SERVER_PORT:8080} port: ${SERVER_PORT:8080}
@@ -50,7 +48,7 @@ springdoc:
path: /swagger-ui path: /swagger-ui
jwt: jwt:
secret: ${JWT_SECRET:change_me_please_make_this_at_least_32_characters_long_for_security} secret: ${JWT_SECRET}
expiration: ${JWT_EXPIRATION:86400000} expiration: ${JWT_EXPIRATION:86400000}
stripe: stripe:

View File

@@ -7,7 +7,7 @@ Made by **Group 2**, Shiv, Nikitha, Alex, Harkamal.
## Requirements ## Requirements
- IntelliJ IDEA (Community or Ultimate) - IntelliJ IDEA (Community or Ultimate)
- Java 17+ - Java 25+
- Maven (handled through IntelliJ) - Maven (handled through IntelliJ)
- Docker and Docker Compose (for the local MySQL container) - Docker and Docker Compose (for the local MySQL container)