Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Comment on lines +80 to +83

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether custom Spring Security configuration exists.
# Expected: at least one SecurityFilterChain bean suitable for API usage.

rg -nP --type=java -C3 'SecurityFilterChain|@EnableWebSecurity|HttpSecurity|csrf\s*\(' src/main/java
rg -nP --type=java -C3 '@Bean\s+.*SecurityFilterChain'

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


Add explicit Spring Security configuration to handle API requests properly.

The spring-boot-starter-security dependency has been added but no SecurityFilterChain bean exists to configure API-specific security (CSRF handling, authentication endpoints, session policies). Without this configuration, API write operations will return 401/403 errors and may trigger unexpected login redirects.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pom.xml` around lines 80 - 83, Create a new configuration class (e.g.,
ApiSecurityConfig) and add a `@Bean` method SecurityFilterChain
securityFilterChain(HttpSecurity http) that configures API-specific security:
disable formLogin and default login redirects, disable or selectively configure
CSRF for stateless API endpoints, set sessionManagement to
SessionCreationPolicy.STATELESS, configure exceptionHandling to return 401/403
(instead of redirect), permit anonymous access to endpoints like /api/auth/**
and /api/public/** and require authentication for other /api/** paths, and
enable CORS; also expose a PasswordEncoder bean (e.g., BCryptPasswordEncoder)
and, if you use JWT, ensure AuthenticationManager/ Jwt filters are wired into
the filter chain. Ensure the class and method names (ApiSecurityConfig and
securityFilterChain) are used so reviewers can find and verify the
configuration.

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.example.vet1177.controllers;

import jakarta.validation.Valid;
import org.example.vet1177.dto.request.comment.CreateCommentRequest;
import org.example.vet1177.dto.request.comment.UpdateCommentRequest;
import org.example.vet1177.dto.response.comment.CommentResponse;
import org.example.vet1177.entities.User;
import org.example.vet1177.services.CommentService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/comments")
public class CommentController {

private final CommentService commentService;

public CommentController(CommentService commentService) {
this.commentService = commentService;
}

// POST /api/comments
@PostMapping
@Transactional
public ResponseEntity<CommentResponse> create(
@Valid @RequestBody CreateCommentRequest request,
@AuthenticationPrincipal User currentUser) {

Comment on lines +30 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify principal/type compatibility and all affected injection points.
# Expected:
# - User entity should implement UserDetails OR controller should consume UserDetails/custom principal.
# - All `@AuthenticationPrincipal` usages should align with configured Authentication principal type.

rg -nP --type=java -C3 'class\s+User\b|implements\s+UserDetails' src/main/java/org/example/vet1177/entities/User.java
rg -nP --type=java -C2 '@AuthenticationPrincipal\s+User\b|@AuthenticationPrincipal\s+.*UserDetails\b' src/main/java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 2439


🏁 Script executed:

# Find security configuration files
fd -e java -e yml -e yaml -e properties | xargs grep -l "SecurityConfig\|UserDetailsService\|@EnableWebSecurity\|AuthenticationProvider" 2>/dev/null | head -20

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Search for UserDetailsService implementation
rg -n "implements UserDetailsService|class.*UserDetailsService" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Check if User is registered or used as principal in any configuration
rg -n "User\s*principal|new User|authentication.*User" --type=java -C2 | head -50

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 601


🏁 Script executed:

# Search for any Spring Security configuration (including annotations)
rg -n "@EnableWebSecurity|@Configuration|SecurityFilterChain|WebSecurityConfigurerAdapter" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Search for authentication/security in application.properties or application.yml
fd -e properties -e yml -e yaml | xargs cat 2>/dev/null

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 1150


🏁 Script executed:

# Check if there's a custom principal class or custom UserDetails implementation
rg -n "implements UserDetails|extends User|class.*Principal" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Look for authentication manager or provider configuration
rg -n "AuthenticationManager|AuthenticationProvider|DaoAuthenticationProvider" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Search more broadly for any Spring Security related code
rg -n "spring.security|@Bean.*SecurityFilterChain|UserDetailsService" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Check pom.xml or build.gradle for spring-security dependency
fd -e pom.xml -e gradle.build | xargs cat 2>/dev/null | grep -i "spring-security\|security"

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# List all Java files in the project to understand structure
find src -name "*.java" -type f | head -30

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 2143


🏁 Script executed:

# Check the entire User entity class definition
cat -n src/main/java/org/example/vet1177/entities/User.java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 4598


🏁 Script executed:

# Check if Spring Security is even a dependency - search pom.xml in root
find . -name "pom.xml" -o -name "build.gradle" | head -5

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 97


🏁 Script executed:

# Search for any authentication annotation usage or auth filter
rg -n "@RequestHeader.*Authorization|@CookieValue|HttpServletRequest.*getHeader|Authorization" --type=java | head -20

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Check if there's any servlet filter or interceptor for authentication
rg -n "Filter|Interceptor|@Component|@WebFilter" --type=java | grep -i "auth\|security\|filter" | head -20

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


🏁 Script executed:

# Look at the main application file to see what's configured
cat -n src/main/java/org/example/vet1177/Vet1177Application.java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 504


🏁 Script executed:

# Check pom.xml for dependencies, especially spring-security
cat -n pom.xml | head -100

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 4027


🏁 Script executed:

# Search for any Bean definitions that might handle authentication
rg -n "@Bean" --type=java

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 70


@AuthenticationPrincipal User is unsafe—User entity does not implement UserDetails.

Lines 32, 50, 66, and 79 inject @AuthenticationPrincipal User currentUser, but User is a JPA entity without UserDetails implementation. With Spring Security auto-configuration active, the principal type mismatch will cause null injection or runtime ClassCastException when authentication is triggered, breaking all authenticated endpoints.

Use @AuthenticationPrincipal UserDetails principal and resolve the domain user separately:

 public ResponseEntity<CommentResponse> create(
         `@Valid` `@RequestBody` CreateCommentRequest request,
-        `@AuthenticationPrincipal` User currentUser) {
+        `@AuthenticationPrincipal` UserDetails principal) {
+
+    User currentUser = userService.findByEmail(principal.getUsername());

Apply the same pattern to getByRecord (line 50), update (line 66), and delete (line 79).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ResponseEntity<CommentResponse> create(
@Valid @RequestBody CreateCommentRequest request,
@AuthenticationPrincipal User currentUser) {
public ResponseEntity<CommentResponse> create(
`@Valid` `@RequestBody` CreateCommentRequest request,
`@AuthenticationPrincipal` UserDetails principal) {
User currentUser = userService.findByEmail(principal.getUsername());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/controllers/CommentController.java` around
lines 30 - 33, The controller methods create, getByRecord, update, and delete
currently inject `@AuthenticationPrincipal` User which is unsafe because User is a
JPA entity and not a UserDetails; change each method signature to accept
`@AuthenticationPrincipal` UserDetails principal (e.g., in create, getByRecord,
update, delete) and then resolve the domain User inside the method via your
UserRepository (e.g., userRepository.findByUsername(principal.getUsername()) or
userService.loadByUsername) and handle the case where the domain User is missing
(return 401/404 as appropriate); ensure you update references to the old User
parameter in these methods to use the resolved domain User and keep
request/response types (CreateCommentRequest, CommentResponse) unchanged.

return ResponseEntity.ok(
CommentResponse.from(
commentService.create(
request.recordId(),
request.body(),
currentUser
)
)
);
}

// GET /api/comments/record/{recordId}
@GetMapping("/record/{recordId}")
@Transactional(readOnly = true)
public ResponseEntity<List<CommentResponse>> getByRecord(
@PathVariable UUID recordId,
@AuthenticationPrincipal User currentUser) {

return ResponseEntity.ok(
commentService.getByRecord(recordId, currentUser)
.stream()
.map(CommentResponse::from)
.toList()
);
}

// PUT /api/comments/{id}
@PutMapping("/{id}")
@Transactional
public ResponseEntity<CommentResponse> update(
@PathVariable UUID id,
@Valid @RequestBody UpdateCommentRequest request,
@AuthenticationPrincipal User currentUser) {

return ResponseEntity.ok(
CommentResponse.from(
commentService.update(id, request.body(), currentUser)
)
);
}
// DELETE /api/comments/{id}
@DeleteMapping("/{id}")
@Transactional
public ResponseEntity<Void> delete(
@PathVariable UUID id,
@AuthenticationPrincipal User currentUser) {

commentService.delete(id, currentUser);
return ResponseEntity.noContent().build();
}

// CommentController — lägg till currentUser
@GetMapping("/record/{recordId}/count")
@Transactional(readOnly = true)
public ResponseEntity<Long> countByRecord(
@PathVariable UUID recordId,
@AuthenticationPrincipal User currentUser) {
return ResponseEntity.ok(
commentService.countByRecord(recordId, currentUser)
);
}




}
28 changes: 28 additions & 0 deletions src/main/java/org/example/vet1177/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.vet1177.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // ← temporärt, öppnar allt
)
Comment on lines +17 to +20

@coderabbitai coderabbitai Bot Mar 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify routing auth rules and principal-based controller usage are aligned.
rg -nP --type=java -C3 'anyRequest\(\)\.permitAll\(|requestMatchers\(.+\)\.authenticated\(' src/main/java/org/example/vet1177
rg -nP --type=java -C2 '@AuthenticationPrincipal\s+User\b' src/main/java/org/example/vet1177/controllers

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 3625


🏁 Script executed:

#!/bin/bash
# Check CommentController's request mapping and all endpoints
rg -nP --type=java -B5 'class CommentController|@RequestMapping|@PostMapping|@GetMapping|@PutMapping|@DeleteMapping' src/main/java/org/example/vet1177/controllers/CommentController.java | head -40

# Check if other controllers also use `@AuthenticationPrincipal`
rg -nP --type=java '@AuthenticationPrincipal\s+User\b' src/main/java/org/example/vet1177/controllers/ | grep -v CommentController

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 1000


🏁 Script executed:

#!/bin/bash
# Find all controllers in the codebase
find src/main/java/org/example/vet1177/controllers -name "*.java" -type f

# Check all controllers for authentication requirements
rg -nP --type=java '@RestController|@Controller|@RequestMapping|@AuthenticationPrincipal' src/main/java/org/example/vet1177/controllers

Repository: ithsjava25/project-backend-org-random-coders

Length of output: 994


Do not ship with global permitAll on all routes.

Line 19 of SecurityConfig.java opens every endpoint without authentication. The CommentController endpoints (all 5 methods at /api/comments/**) require @AuthenticationPrincipal User but are currently accessible without authentication, creating a critical security gap.

🔐 Suggested hardening
 return http
         .csrf(csrf -> csrf.disable())
         .authorizeHttpRequests(auth -> auth
-                .anyRequest().permitAll()  // ← temporärt, öppnar allt
+                .requestMatchers("/api/comments/**").authenticated()
+                .anyRequest().permitAll()
         )
         .build();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // ← temporärt, öppnar allt
)
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/comments/**").authenticated()
.anyRequest().permitAll()
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/vet1177/security/SecurityConfig.java` around lines
17 - 20, SecurityConfig currently allows all requests via
anyRequest().permitAll(), leaving CommentController endpoints
("/api/comments/**" which expect `@AuthenticationPrincipal` User) unprotected;
update SecurityConfig to remove the global permitAll and explicitly require
authentication for the comment routes (e.g. configure
authorizeHttpRequests/authorizeRequests so "/api/comments/**" -> authenticated()
and non-sensitive routes -> permitAll()), keeping CSRF/other settings as
appropriate; reference the authorizeHttpRequests call in SecurityConfig and the
CommentController path "/api/comments/**" and `@AuthenticationPrincipal` User when
making the change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a temporary security configuration to unblock
development and testing. Full JWT implementation with
JwtAuthFilter, stateless sessions and proper endpoint
restrictions will be added in feat/spring-security-config.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@annikaholmqvist94, understood — thanks for the context! That makes sense as a development unblocking strategy. Just a friendly reminder to ensure the feat/spring-security-config branch enforces authentication on /api/comments/** (and other protected routes) before this reaches production, since the @AuthenticationPrincipal User parameters in CommentController will silently receive null if the endpoints remain unauthenticated at that point.

Would you like me to open a follow-up issue to track the JWT/security hardening work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo ithsjava25/project-backend-org-random-coders \
  --title "feat: Implement JWT security hardening (JwtAuthFilter, stateless sessions, endpoint restrictions)" \
  --assignee annikaholmqvist94 \
  --body "## Summary

This issue tracks the full JWT/Spring Security implementation that will replace the temporary permissive \`SecurityConfig\` introduced in PR `#55`.

## Background

PR `#55` ([feat/comment-controller](https://github.com/ithsjava25/project-backend-org-random-coders/pull/55)) added a \`SecurityConfig\` with \`anyRequest().permitAll()\` as a temporary measure to unblock development and testing. This must be hardened before reaching production.

## Tasks

- [ ] Implement \`JwtAuthFilter\` to validate JWT tokens on incoming requests
- [ ] Configure stateless session management (\`SessionCreationPolicy.STATELESS\`)
- [ ] Restrict \`/api/comments/**\` to authenticated users only (replacing the global \`permitAll\`)
- [ ] Restrict other protected routes as appropriate
- [ ] Ensure \`@AuthenticationPrincipal User\` in \`CommentController\` receives a valid non-null principal when endpoints are called with a valid token

## References

- PR `#55` comment: https://github.com/ithsjava25/project-backend-org-random-coders/pull/55#discussion_r3009802568
- Requested by: `@annikaholmqvist94`

## Notes

Until this is implemented, the \`@AuthenticationPrincipal User\` parameters in \`CommentController\` will silently receive \`null\` for unauthenticated requests."

Length of output: 73


@annikaholmqvist94, done! I've opened issue #61 to track the JWT security hardening work:
👉 #61

It covers:

  • Implementing JwtAuthFilter
  • Configuring stateless session management
  • Restricting /api/comments/** (and other protected routes) to authenticated users
  • Ensuring @AuthenticationPrincipal User receives a valid non-null principal

.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.example.vet1177.services;

import org.example.vet1177.entities.*;
import org.example.vet1177.exception.BusinessRuleException;
import org.example.vet1177.exception.ResourceNotFoundException;
import org.example.vet1177.policy.CommentPolicy;
import org.example.vet1177.repository.CommentRepository;
Expand Down Expand Up @@ -72,7 +71,12 @@ public void delete(UUID commentId, User currentUser) {
}

@Transactional(readOnly = true)
public long countByRecord(UUID recordId) {
public long countByRecord(UUID recordId, User currentUser) {
MedicalRecord record = medicalRecordRepository.findById(recordId)
.orElseThrow(() -> new ResourceNotFoundException("MedicalRecord", recordId));

commentPolicy.canView(currentUser, record); // ← åtkomstkontroll

return commentRepository.countByMedicalRecordId(recordId);
}
}