Conversation
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. 💤 Files selected but had no reviewable changes (1)
⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds JWT-based stateless authentication (services, filter, security config), auth endpoints (login/register/refresh) with persisted refresh tokens, user and refresh-token JPA entities/repositories, global exception handling, S3 presigned upload support, MapStruct and dependency updates, and multiple DTOs/controllers. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthController
participant AuthService
participant UserRepository
participant PasswordEncoder
participant RefreshTokenService
participant RefreshTokenRepository
participant JwtService
Client->>AuthController: POST /api/auth/login (email,password)
AuthController->>AuthService: login(request)
AuthService->>UserRepository: findByEmail(email)
UserRepository-->>AuthService: UserEntity
AuthService->>PasswordEncoder: matches(raw, hash)
PasswordEncoder-->>AuthService: boolean
AuthService->>JwtService: generateAccessToken(userDetails)
JwtService-->>AuthService: accessToken
AuthService->>RefreshTokenService: generateRefreshToken()
RefreshTokenService-->>AuthService: refreshToken
AuthService->>RefreshTokenRepository: save(refreshTokenEntity)
RefreshTokenRepository-->>AuthService: savedEntity
AuthService-->>AuthController: AuthResponse
AuthController-->>Client: 200 OK (tokens)
sequenceDiagram
participant Client
participant JwtAuthenticationFilter
participant JwtService
participant CustomUserDetailsService
participant SecurityContextHolder
participant SecuredEndpoint
Client->>JwtAuthenticationFilter: HTTP request (Authorization: Bearer ...)
JwtAuthenticationFilter->>JwtService: extractUsername(token)
JwtService-->>JwtAuthenticationFilter: email
JwtAuthenticationFilter->>CustomUserDetailsService: loadUserByUsername(email)
CustomUserDetailsService-->>JwtAuthenticationFilter: UserDetailsImpl
JwtAuthenticationFilter->>JwtService: isTokenValid(token, userDetails)
JwtService-->>JwtAuthenticationFilter: boolean
JwtAuthenticationFilter->>SecurityContextHolder: setAuthentication(...)
JwtAuthenticationFilter->>SecuredEndpoint: forward request
SecuredEndpoint-->>Client: 200 OK / secured response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (1)
24-33: Consider extracting shared error-response construction.The same DTO + response assembly is repeated across handlers. A small helper will reduce duplication and future drift.
Also applies to: 40-49, 60-69
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 24 - 33, Extract the repeated ErrorResponseDto creation and ResponseEntity assembly into a private helper method on GlobalExceptionHandler (e.g., buildErrorResponse or toResponseEntity) that accepts the error code string, message (or Exception e), and HttpStatus; replace the duplicated blocks (currently creating ErrorResponseDto and returning ResponseEntity.status(...).body(...)) in the handlers (the blocks around lines with ErrorResponseDto construction) by calls to this helper so all handlers (including the ones at the other noted ranges) use the single method to create the DTO and ResponseEntity.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java (1)
10-10: Rename method parameter to match query semantics.Use
loginin Line 10 for clarity and consistency withfindByEmail.Suggested patch
- Optional<UserEntity> findByEmail(String login); + Optional<UserEntity> findByEmail(String email);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java` at line 10, Rename the method parameter in AuthRepository.findByEmail from "login" to "email" to match the method name and query semantics; update the method signature in the AuthRepository interface (findByEmail(String email)) and then update any implementing classes, unit tests, and callers that reference the old parameter name to use "email" so compilation stays consistent.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
38-38: PreferHttpStatus.CREATEDover numeric literal for response code.Line 38 should use the semantic constant instead of
201to improve readability.Suggested patch
+import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` at line 38, Replace the numeric literal 201 with the semantic HttpStatus constant in the AuthController registration response: change ResponseEntity.status(201) to ResponseEntity.status(HttpStatus.CREATED) (or use ResponseEntity.created(...) as an alternative) and ensure HttpStatus is imported; update the register handler method that calls authService.register(request) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/smartjam-api/build.gradle`:
- Around line 6-8: Update the pinned JJWT coordinates to use the JJWT BOM and
the current release (e.g., 0.13.0): add the platform BOM dependency
(platform('io.jsonwebtoken:jjwt-bom:0.13.0')) in the dependencies block and
remove explicit versions from the module coordinates so the three artifacts
(io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl,
io.jsonwebtoken:jjwt-jackson) are declared without versions; ensure
implementation uses io.jsonwebtoken:jjwt-api and runtimeOnly uses
io.jsonwebtoken:jjwt-impl and io.jsonwebtoken:jjwt-jackson so the BOM controls
the version.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java`:
- Around line 23-30: SecurityConfig currently enables HTTP Basic and stateful
sessions; change it to JWT bearer-style stateless auth by disabling httpBasic(),
adding
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS), and
wiring a JwtAuthenticationFilter/oncePerRequestFilter to validate tokens; update
SecurityConfig's authorizeHttpRequests() to expect Bearer tokens for protected
endpoints. Implement JWT creation in AuthService.login() and
AuthService.register(): after validating/creating the user, build a signed JWT
(using Jwts.builder() with subject, claims, issuedAt, expiration and the signing
key from config) and return it in AuthResponse (add a token field if missing).
Ensure token parsing/validation logic is used by the filter (use the same
signing key/secret) and throw appropriate authentication exceptions when token
is invalid or expired.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java`:
- Around line 3-6: The LoginRequest record lacks validation annotations so
`@Valid` in AuthController does nothing; add jakarta.validation.constraints
imports and annotate the email component with `@NotBlank` and `@Email`, and annotate
the password component with `@NotBlank` and `@Size`(min = 8, max = 72) on the
LoginRequest record definition so bean validation runs for AuthController;
ensure import statements for jakarta.validation.constraints.NotBlank, Email, and
Size are added at the top of the file.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`:
- Around line 3-7: RegisterRequest's record components lack Bean Validation
annotations so `@Valid` on controller params does nothing; add field-level
constraints (e.g., `@NotBlank` on username and password, `@Email` and `@NotBlank` on
email, and any size constraints) to the record components declaration in
RegisterRequest and do the same for LoginRequest (apply `@NotBlank` to its
username/email and password components) so Spring's validator rejects
blank/invalid inputs before reaching the service layer. Ensure imports for
javax.validation.constraints.* (or jakarta.validation.constraints.* depending on
project) are added and that the record component names remain unchanged.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 24-27: The handlers in GlobalExceptionHandler currently place raw
exception text into ErrorResponseDto via e.getMessage() (e.g., where
ErrorResponseDto is constructed), which can leak internals; instead replace the
response message with a sanitized, user-friendly constant (e.g., "An unexpected
error occurred" or context-appropriate safe text) and log the raw exception and
stack trace to the server logger (logger.error("...", e)) so diagnostics are
preserved; apply this change to all places that build ErrorResponseDto from
e.getMessage() in GlobalExceptionHandler (including the other handler blocks
that construct ErrorResponseDto).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 28-33: The current AuthService authentication path leaks different
failure reasons (repository.findByEmail(...) throws NoSuchElementException vs
passwordEncoder.matches(...) throws IllegalStateException); change to return a
single generic authentication failure for both cases: in the AuthService method
that performs login (the block using repository.findByEmail, UserEntity, and
passwordEncoder.matches), catch the case where the user is not found and the
case where the password does not match and throw the same generic
exception/message (e.g., "Authentication failed") or return the same error
response type; ensure you remove distinct messages like "Login not found..." and
"Invalid password" and use a single uniform error path for both failure
scenarios.
- Around line 39-51: Replace the check-then-save race by removing the pre-save
existence check in AuthService and instead wrap repository.save(userEntity) in a
try-catch that catches the DB uniqueness violation (e.g.,
DataIntegrityViolationException or ConstraintViolationException) and rethrows
the same IllegalStateException("The account exists, try login, please"); keep
building UserEntity (setUsername, setEmail, setPasswordHash via passwordEncoder,
setRole) as before and perform the save inside the try block so concurrent
creates are handled by translating the DB exception into the intended error.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java`:
- Line 9: The main entry method in SmartjamApiApplication currently has
package-private visibility; change the method signature of main to be public so
it matches the JVM-required entry point (i.e., update the method declaration for
main to public static void main(String[] args)) in the SmartjamApiApplication
class to restore normal application startup.
---
Nitpick comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Line 38: Replace the numeric literal 201 with the semantic HttpStatus constant
in the AuthController registration response: change ResponseEntity.status(201)
to ResponseEntity.status(HttpStatus.CREATED) (or use ResponseEntity.created(...)
as an alternative) and ensure HttpStatus is imported; update the register
handler method that calls authService.register(request) accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 24-33: Extract the repeated ErrorResponseDto creation and
ResponseEntity assembly into a private helper method on GlobalExceptionHandler
(e.g., buildErrorResponse or toResponseEntity) that accepts the error code
string, message (or Exception e), and HttpStatus; replace the duplicated blocks
(currently creating ErrorResponseDto and returning
ResponseEntity.status(...).body(...)) in the handlers (the blocks around lines
with ErrorResponseDto construction) by calls to this helper so all handlers
(including the ones at the other noted ranges) use the single method to create
the DTO and ResponseEntity.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java`:
- Line 10: Rename the method parameter in AuthRepository.findByEmail from
"login" to "email" to match the method name and query semantics; update the
method signature in the AuthRepository interface (findByEmail(String email)) and
then update any implementing classes, unit tests, and callers that reference the
old parameter name to use "email" so compilation stays consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1c0a822e-3e97-4b70-9bf7-8ade5e272e68
📒 Files selected for processing (13)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
backend/smartjam-api/build.gradle
Outdated
| implementation 'io.jsonwebtoken:jjwt-api:0.11.5' | ||
| runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' | ||
| runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Maven Central metadata for jjwt-api (latest release) =="
curl -fsSL https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt-api/maven-metadata.xml 2>/dev/null | grep -E "<latest>|<release>" | head -n 5 || echo "Failed to fetch metadata"
echo
echo "== Checking available versions for jjwt-api =="
curl -fsSL https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt-api/maven-metadata.xml 2>/dev/null | sed -n '/<versions>/,/<\/versions>/p' | grep -oP '(?<=<version>)[^<]+' | tail -n 15 || echo "Failed to fetch versions"
echo
echo "== OSV vulnerability lookup for 0.11.5 =="
curl -fsSL https://api.osv.dev/v1/query \
-H 'Content-Type: application/json' \
-d '{
"package": {"ecosystem":"Maven","name":"io.jsonwebtoken:jjwt-impl"},
"version":"0.11.5"
}' 2>/dev/null | head -c 500 || echo "Failed to query OSV"
echo
echo "== Checking if jjwt-bom exists =="
curl -fsSL -w "\nHTTP Status: %{http_code}\n" https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt-bom/0.13.0/jjwt-bom-0.13.0.pom 2>/dev/null | head -n 10 || echo "BOM check failed"Repository: Satlykovs/SmartJam
Length of output: 723
Upgrade JJWT from 0.11.5 to maintain a current, supported release line.
Lines 6–8 pin JJWT to version 0.11.5, which is significantly outdated (0.13.0 is the current release). For authentication-critical code, using an old release increases maintenance burden and misses numerous bug fixes and improvements from intervening versions. Upgrade to the latest release and manage the three artifacts via a BOM to centralize version control:
Proposed Gradle update (BOM + unpinned module coordinates)
dependencies {
- implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
- runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
- runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
+ implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0')
+ implementation 'io.jsonwebtoken:jjwt-api'
+ runtimeOnly 'io.jsonwebtoken:jjwt-impl'
+ runtimeOnly 'io.jsonwebtoken:jjwt-jackson'📝 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.
| implementation 'io.jsonwebtoken:jjwt-api:0.11.5' | |
| runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' | |
| runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' | |
| implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0') | |
| implementation 'io.jsonwebtoken:jjwt-api' | |
| runtimeOnly 'io.jsonwebtoken:jjwt-impl' | |
| runtimeOnly 'io.jsonwebtoken:jjwt-jackson' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/smartjam-api/build.gradle` around lines 6 - 8, Update the pinned JJWT
coordinates to use the JJWT BOM and the current release (e.g., 0.13.0): add the
platform BOM dependency (platform('io.jsonwebtoken:jjwt-bom:0.13.0')) in the
dependencies block and remove explicit versions from the module coordinates so
the three artifacts (io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl,
io.jsonwebtoken:jjwt-jackson) are declared without versions; ensure
implementation uses io.jsonwebtoken:jjwt-api and runtimeOnly uses
io.jsonwebtoken:jjwt-impl and io.jsonwebtoken:jjwt-jackson so the BOM controls
the version.
✅ Confirmed as addressed by @Satlykovs
There was a problem hiding this comment.
Он прав, версия устарела, используй 0.13.0, можешь смотреть версии на https://mvnrepository.com/
There was a problem hiding this comment.
@Satlykovs, согласен! Последний релиз JJWT на Maven Central — 0.13.0 (август 2025 г.), так что это правильный выбор. Также обратите внимание, что переход с 0.12.6 на 0.13.0 включает 6 изменений, а более старые версии имеют известные уязвимости.
0.11.x на 0.13.x содержит breaking changes в API (новый fluent builder API). Поэтому при использовании JJWT в коде (генерация/парсинг токенов) нужно будет обновить и сам код.
Вот готовый патч с использованием BOM для централизованного управления версией:
- implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
- runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
- runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
+ implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0')
+ implementation 'io.jsonwebtoken:jjwt-api'
+ runtimeOnly 'io.jsonwebtoken:jjwt-impl'
+ runtimeOnly 'io.jsonwebtoken:jjwt-jackson'( ˶ᵔ ᵕ ᵔ˶ )
🐇
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
Outdated
Show resolved
Hide resolved
| public record LoginRequest( | ||
| String email, | ||
| String password | ||
| ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "LoginRequest.java" -type fRepository: Satlykovs/SmartJam
Length of output: 145
🏁 Script executed:
find . -name "pom.xml" -o -name "build.gradle" | head -5Repository: Satlykovs/SmartJam
Length of output: 161
🏁 Script executed:
find . -path "*/controller*" -name "*.java" -type f | grep -i loginRepository: Satlykovs/SmartJam
Length of output: 44
🏁 Script executed:
cat -n ./backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javaRepository: Satlykovs/SmartJam
Length of output: 221
🏁 Script executed:
cat ./backend/smartjam-api/build.gradleRepository: Satlykovs/SmartJam
Length of output: 802
🏁 Script executed:
find ./backend/smartjam-api -path "*/controller*" -name "*.java" -type fRepository: Satlykovs/SmartJam
Length of output: 154
🏁 Script executed:
cat -n ./backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javaRepository: Satlykovs/SmartJam
Length of output: 1592
Add validation constraints to LoginRequest DTO fields.
The @Valid annotation in AuthController (line 27) requires constraints on the LoginRequest fields. Without them, no validation occurs and malformed login payloads are accepted. Add @NotBlank and @Email to the email field, and @NotBlank with @Size(min = 8, max = 72) to the password field. Ensure jakarta.validation.constraints imports are added.
Suggested patch
package com.smartjam.smartjamapi.dto;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
public record LoginRequest(
- String email,
- String password
+ `@NotBlank` `@Email` String email,
+ `@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}📝 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.
| public record LoginRequest( | |
| String email, | |
| String password | |
| ) { | |
| package com.smartjam.smartjamapi.dto; | |
| import jakarta.validation.constraints.Email; | |
| import jakarta.validation.constraints.NotBlank; | |
| import jakarta.validation.constraints.Size; | |
| public record LoginRequest( | |
| `@NotBlank` `@Email` String email, | |
| `@NotBlank` `@Size`(min = 8, max = 72) String password | |
| ) { | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java`
around lines 3 - 6, The LoginRequest record lacks validation annotations so
`@Valid` in AuthController does nothing; add jakarta.validation.constraints
imports and annotate the email component with `@NotBlank` and `@Email`, and annotate
the password component with `@NotBlank` and `@Size`(min = 8, max = 72) on the
LoginRequest record definition so bean validation runs for AuthController;
ensure import statements for jakarta.validation.constraints.NotBlank, Email, and
Size are added at the top of the file.
There was a problem hiding this comment.
Ты используешь валидацию в контроллере, но пока не накинул аннотаций в DTO-шке, не забудь
There was a problem hiding this comment.
@Satlykovs, именно так — без аннотаций на полях DTO аннотация @Valid в контроллере не даст никакого эффекта. Вот чистый патч для LoginRequest.java:
package com.smartjam.smartjamapi.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record LoginRequest(
`@NotBlank` `@Email` String email,
`@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}Аналогично стоит добавить аннотации и в RegisterRequest:
package com.smartjam.smartjamapi.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record RegisterRequest(
`@NotBlank` `@Size`(min = 3, max = 50) String username,
`@NotBlank` `@Email` String email,
`@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}| public record RegisterRequest( | ||
| String username, | ||
| String email, | ||
| String password | ||
| ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's verify the current state of RegisterRequest
echo "=== RegisterRequest current state ==="
find . -name "RegisterRequest.java" -type f -exec cat -n {} \;
echo -e "\n=== AuthController usage of RegisterRequest ==="
find . -name "AuthController.java" -type f -exec cat -n {} \;
echo -e "\n=== Check for validation annotations in the project ==="
rg -l "@Valid|@NotBlank|@Email|@Size" --type=java | head -20Repository: Satlykovs/SmartJam
Length of output: 2030
🏁 Script executed:
#!/bin/bash
# Check if Bean Validation dependencies are present
echo "=== Check pom.xml for validation dependencies ==="
find . -name "pom.xml" -type f -exec rg -A5 -B5 "validation|jakarta" {} \;
echo -e "\n=== Check gradle files ==="
find . -name "*.gradle" -o -name "*.gradle.kts" | xargs rg -A5 -B5 "validation|jakarta" 2>/dev/null || echo "No gradle files with validation deps found"Repository: Satlykovs/SmartJam
Length of output: 824
🏁 Script executed:
#!/bin/bash
# Check LoginRequest as it also uses `@Valid`
echo "=== LoginRequest current state ==="
find . -name "LoginRequest.java" -type f -exec cat -n {} \;Repository: Satlykovs/SmartJam
Length of output: 256
Add field-level validation constraints for authentication request DTOs.
@Valid on controller parameters is ineffective for RegisterRequest and LoginRequest because the record components lack validation annotations. This allows blank or invalid credentials to pass into the service layer.
The validation framework is available via spring-boot-starter-validation, so the fix is straightforward:
Suggested patch for RegisterRequest
package com.smartjam.smartjamapi.dto;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
public record RegisterRequest(
- String username,
- String email,
- String password
+ `@NotBlank` String username,
+ `@NotBlank` `@Email` String email,
+ `@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}Suggested patch for LoginRequest
package com.smartjam.smartjamapi.dto;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
public record LoginRequest(
- String email,
- String password
+ `@NotBlank` `@Email` String email,
+ `@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`
around lines 3 - 7, RegisterRequest's record components lack Bean Validation
annotations so `@Valid` on controller params does nothing; add field-level
constraints (e.g., `@NotBlank` on username and password, `@Email` and `@NotBlank` on
email, and any size constraints) to the record components declaration in
RegisterRequest and do the same for LoginRequest (apply `@NotBlank` to its
username/email and password components) so Spring's validator rejects
blank/invalid inputs before reaching the service layer. Ensure imports for
javax.validation.constraints.* (or jakarta.validation.constraints.* depending on
project) are added and that the record component names remain unchanged.
There was a problem hiding this comment.
Ты используешь валидацию в контроллере, но пока не накинул аннотаций в DTO-шке, не забудь
There was a problem hiding this comment.
@Satlykovs, да, именно — это и есть суть предыдущего замечания. Аннотации @Valid на параметрах контроллера ничего не дают без constraint-аннотаций на компонентах самих record'ов.
Нужно добавить в оба DTO следующее:
RegisterRequest.java:
package com.smartjam.smartjamapi.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record RegisterRequest(
`@NotBlank` String username,
`@NotBlank` `@Email` String email,
`@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}LoginRequest.java:
package com.smartjam.smartjamapi.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record LoginRequest(
`@NotBlank` `@Email` String email,
`@NotBlank` `@Size`(min = 8, max = 72) String password
) {
}Зависимость spring-boot-starter-validation уже есть в build.gradle, так что дополнительных изменений в конфигурации не потребуется.
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Outdated
Show resolved
Hide resolved
| UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow( | ||
| () -> new NoSuchElementException("Login not found, try register, please") | ||
| ); | ||
| if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { | ||
| throw new IllegalStateException("Invalid password"); | ||
| } |
There was a problem hiding this comment.
Use a single generic auth failure response.
Line 28 and Line 32 currently expose different failure reasons (email not found vs invalid password), which enables account enumeration. Return one identical error path/message for both cases.
Suggested change
- UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow(
- () -> new NoSuchElementException("Login not found, try register, please")
- );
- if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) {
- throw new IllegalStateException("Invalid password");
+ UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow(
+ () -> new IllegalStateException("Invalid credentials")
+ );
+ if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) {
+ throw new IllegalStateException("Invalid credentials");
}📝 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.
| UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow( | |
| () -> new NoSuchElementException("Login not found, try register, please") | |
| ); | |
| if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { | |
| throw new IllegalStateException("Invalid password"); | |
| } | |
| UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow( | |
| () -> new IllegalStateException("Invalid credentials") | |
| ); | |
| if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { | |
| throw new IllegalStateException("Invalid credentials"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`
around lines 28 - 33, The current AuthService authentication path leaks
different failure reasons (repository.findByEmail(...) throws
NoSuchElementException vs passwordEncoder.matches(...) throws
IllegalStateException); change to return a single generic authentication failure
for both cases: in the AuthService method that performs login (the block using
repository.findByEmail, UserEntity, and passwordEncoder.matches), catch the case
where the user is not found and the case where the password does not match and
throw the same generic exception/message (e.g., "Authentication failed") or
return the same error response type; ensure you remove distinct messages like
"Login not found..." and "Invalid password" and use a single uniform error path
for both failure scenarios.
There was a problem hiding this comment.
Он прав, ты по факту помогаешь хацкерам получить инфу, т.к у тебя в случае если аккаунт верный, то полетит другая ошибка. Лучше всегда возвращать унифицировванную ошибку "Неверный логин или пароль"
There was a problem hiding this comment.
@Satlykovs, абсолютно верно! Это классическая уязвимость перечисления аккаунтов (Account Enumeration). Атакующий может автоматически перебирать email-адреса и по разным ответам сервера определять, какие из них зарегистрированы в системе.
Рекомендуемое исправление — использовать единое сообщение для обоих случаев провала аутентификации:
public AuthResponse login(LoginRequest request) {
UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow(
() -> new IllegalStateException("Неверный логин или пароль")
);
if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) {
throw new IllegalStateException("Неверный логин или пароль");
}
// ...
}Таким образом, ни по HTTP-статусу, ни по тексту ошибки невозможно определить, существует ли аккаунт с таким email.
(\_/)
(•‿•)
(> 🔒
| boolean exists = repository.findByEmail(request.email()).isPresent(); | ||
|
|
||
| if (exists) { | ||
| throw new IllegalStateException("The account exists, try login, please"); | ||
| } | ||
|
|
||
| UserEntity userEntity = new UserEntity(); | ||
| userEntity.setUsername(request.username()); | ||
| userEntity.setEmail(request.email()); | ||
| userEntity.setPasswordHash(passwordEncoder.encode(request.password())); | ||
| userEntity.setRole("USER"); | ||
|
|
||
| repository.save(userEntity); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check email uniqueness in entity definitions ==="
fd 'UserEntity\.java$' -t f | xargs -r rg -n -C3 '@Column|@Table|unique|email'
echo
echo "=== Check DB migrations for unique email constraint/index ==="
fd -e sql -e yml -e yaml -e xml -t f | xargs -r rg -n -C2 'unique.*email|email.*unique|create unique index|constraint.*email'
echo
echo "=== Check auth error mapping for registration conflicts ==="
fd 'AuthService\.java$|GlobalExceptionHandler\.java$' -t f | xargs -r rg -n -C3 'register\(|findByEmail|save\(|DataIntegrityViolationException|IllegalStateException'Repository: Satlykovs/SmartJam
Length of output: 1011
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check if GlobalExceptionHandler exists and handles DataIntegrityViolationException ==="
fd 'GlobalExceptionHandler\|ControllerAdvice\|ExceptionHandler' -t f | head -20
echo
echo "=== Full AuthService.java to see complete context ==="
fd 'AuthService\.java$' -t f | xargs -r cat -n
echo
echo "=== Check for any DataIntegrityViolationException handling ==="
rg 'DataIntegrityViolationException' -t java -C 5Repository: Satlykovs/SmartJam
Length of output: 2647
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check Spring Data/Hibernate configuration ==="
fd -e yml -e yaml -e properties -t f | xargs -r rg -C 3 'spring\.jpa|hibernate|ddl-auto|schema'Repository: Satlykovs/SmartJam
Length of output: 1879
Handle database uniqueness violations in registration to prevent unhandled exceptions on concurrent requests.
The email uniqueness constraint is already defined at the database level (@Column(nullable = false, unique = true) in UserEntity), but the current check-then-save pattern creates a race condition: concurrent requests for the same email can both pass the existence check and attempt to save simultaneously, causing one to fail with an unhandled DataIntegrityViolationException. Remove the pre-save check and catch the exception instead, converting it to the intended error message.
Suggested change
+import org.springframework.dao.DataIntegrityViolationException;
@@
public AuthResponse register(RegisterRequest request) {
- boolean exists = repository.findByEmail(request.email()).isPresent();
-
- if (exists) {
- throw new IllegalStateException("The account exists, try login, please");
- }
-
- UserEntity userEntity = new UserEntity();
- userEntity.setUsername(request.username());
- userEntity.setEmail(request.email());
- userEntity.setPasswordHash(passwordEncoder.encode(request.password()));
- userEntity.setRole("USER");
-
- repository.save(userEntity);
+ try {
+ UserEntity userEntity = new UserEntity();
+ userEntity.setUsername(request.username());
+ userEntity.setEmail(request.email());
+ userEntity.setPasswordHash(passwordEncoder.encode(request.password()));
+ userEntity.setRole("USER");
+ repository.save(userEntity);
+ } catch (DataIntegrityViolationException ex) {
+ throw new IllegalStateException("The account exists, try login, please");
+ }
return new AuthResponse("Registration was successful", AvailabilityStatus.AVAILABLE);📝 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.
| boolean exists = repository.findByEmail(request.email()).isPresent(); | |
| if (exists) { | |
| throw new IllegalStateException("The account exists, try login, please"); | |
| } | |
| UserEntity userEntity = new UserEntity(); | |
| userEntity.setUsername(request.username()); | |
| userEntity.setEmail(request.email()); | |
| userEntity.setPasswordHash(passwordEncoder.encode(request.password())); | |
| userEntity.setRole("USER"); | |
| repository.save(userEntity); | |
| try { | |
| UserEntity userEntity = new UserEntity(); | |
| userEntity.setUsername(request.username()); | |
| userEntity.setEmail(request.email()); | |
| userEntity.setPasswordHash(passwordEncoder.encode(request.password())); | |
| userEntity.setRole("USER"); | |
| repository.save(userEntity); | |
| } catch (DataIntegrityViolationException ex) { | |
| throw new IllegalStateException("The account exists, try login, please"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`
around lines 39 - 51, Replace the check-then-save race by removing the pre-save
existence check in AuthService and instead wrap repository.save(userEntity) in a
try-catch that catches the DB uniqueness violation (e.g.,
DataIntegrityViolationException or ConstraintViolationException) and rethrows
the same IllegalStateException("The account exists, try login, please"); keep
building UserEntity (setUsername, setEmail, setPasswordHash via passwordEncoder,
setRole) as before and perform the save inside the try block so concurrent
creates are handled by translating the DB exception into the intended error.
There was a problem hiding this comment.
Он прав, тут может быть Race Condition, у тебя 2 потока могут проверить isPresent() и одновременно попытаться сохранить юзера, тогда всё равно полетит ошибка, так что isPresent() вообще не защищает здесь. Как будто можно просто делать save() и отлавливать ошибку, если уже пользователь существует (например в exception handler-e, который у тебя есть)
There was a problem hiding this comment.
@Satlykovs, именно так! Подход "check-then-save" не является атомарным, поэтому не защищает от гонки. Правильное решение — убрать isPresent() и обернуть save() в try-catch, перехватывая DataIntegrityViolationException (которую Spring Data бросает при нарушении уникального ограничения на уровне БД) и преобразуя её в нужное сообщение об ошибке. Предложенный выше committable suggestion уже содержит готовую реализацию этого подхода. 👆
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
♻️ Duplicate comments (1)
backend/smartjam-api/build.gradle (1)
6-8:⚠️ Potential issue | 🟠 MajorUpgrade JJWT coordinates to the current BOM-managed release.
Line 6–Line 8 still pin
0.11.5, which is outdated for an auth-critical path and duplicates previously raised feedback that remains unresolved.Proposed Gradle update
- implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0') + implementation 'io.jsonwebtoken:jjwt-api' + runtimeOnly 'io.jsonwebtoken:jjwt-impl' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson'#!/bin/bash set -euo pipefail echo "Checking latest JJWT BOM release from Maven Central metadata..." curl -fsSL https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt-bom/maven-metadata.xml \ | grep -E "<latest>|<release>" || true echo echo "Checking currently declared JJWT versions in Gradle file..." rg -n "jjwt-(api|impl|jackson)|jjwt-bom" backend/smartjam-api/build.gradle🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/build.gradle` around lines 6 - 8, Dependency coordinates for JJWT in build.gradle are pinned to the old 0.11.5 versions (implementation 'io.jsonwebtoken:jjwt-api:0.11.5', runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5', runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'), so update to use the BOM-managed release: add the jjwt-bom as a platform/enforcedPlatform in the dependencies block (platform("io.jsonwebtoken:jjwt-bom:<latest-version>")) and remove the explicit version suffixes from the jjwt-api, jjwt-impl and jjwt-jackson coordinates so they resolve to the BOM version; ensure you replace <latest-version> with the current BOM release and keep the artifact IDs (jjwt-api, jjwt-impl, jjwt-jackson) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@backend/smartjam-api/build.gradle`:
- Around line 6-8: Dependency coordinates for JJWT in build.gradle are pinned to
the old 0.11.5 versions (implementation 'io.jsonwebtoken:jjwt-api:0.11.5',
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5', runtimeOnly
'io.jsonwebtoken:jjwt-jackson:0.11.5'), so update to use the BOM-managed
release: add the jjwt-bom as a platform/enforcedPlatform in the dependencies
block (platform("io.jsonwebtoken:jjwt-bom:<latest-version>")) and remove the
explicit version suffixes from the jjwt-api, jjwt-impl and jjwt-jackson
coordinates so they resolve to the BOM version; ensure you replace
<latest-version> with the current BOM release and keep the artifact IDs
(jjwt-api, jjwt-impl, jjwt-jackson) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f9c42c21-7f63-46f3-9bd6-5c32a2a4d23a
📒 Files selected for processing (1)
backend/smartjam-api/build.gradle
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
Outdated
Show resolved
Hide resolved
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Outdated
Show resolved
Hide resolved
|
|
||
| var errorDto = new ErrorResponseDto( | ||
| "INTERNAL_SERVER_ERROR", | ||
| e.getMessage(), |
There was a problem hiding this comment.
Ты выдаешь наружу приватную информацию, так нельзя делать
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Show resolved
Hide resolved
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface AuthRepository extends JpaRepository<UserEntity, Long> { |
There was a problem hiding this comment.
Репозиториии называют по той сущности, которой они управляют. Т.е тут не AuthRepository, а UserRepository
Еще, в схеме бд было указано, что ключ будет UUID, у тебя тут Long
|
|
||
|
|
||
|
|
||
| public AuthService(AuthRepository repository, PasswordEncoder passwordEncoder) { |
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
| userEntity.setUsername(request.username()); | ||
| userEntity.setEmail(request.email()); | ||
| userEntity.setPasswordHash(passwordEncoder.encode(request.password())); | ||
| userEntity.setRole("USER"); |
There was a problem hiding this comment.
Строку для роли использовать плохо, легко ошибиться в названии
There was a problem hiding this comment.
не понял что тут хотят от меня
| private String avatarUrl; | ||
|
|
||
| @Column(nullable = false) | ||
| private String role; |
There was a problem hiding this comment.
Строку для роли использовать не найс, лучше Enum, в котором будут условные STUDENT и TEACHER, + накинуть аннотацию Enumerated(EnumType.STRING)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
Show resolved
Hide resolved
|
P.S: я подключил spotless для форматирования кода, без этого тесты не пройдут, проверь у себя в IDE, там должна быть цель spotlessApply у gradle-а |
|
@coderabbitai please summarize all the feedback from this review (including my comments and yours) into a structured checklist for the author. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 126099
✅ Auth PR — Review Checklist for the Author🔴 Critical / Security
🟠 Major / Architecture
🟡 Code Quality / Conventions
📋 Pre-merge / Process
🧠 Learnings used |
|
@coderabbitai please duplicate this list in russian language if you can |
|
Tip For best results, initiate chat on the files or code changes.
✅ Auth PR — Чеклист для автора🔴 Критично / Безопасность
🟠 Важно / Архитектура
🟡 Качество кода / Соглашения
📋 Перед слиянием / Процесс
🧠 Learnings used |
There was a problem hiding this comment.
Actionable comments posted: 25
♻️ Duplicate comments (4)
backend/smartjam-api/build.gradle (1)
6-8:⚠️ Potential issue | 🟠 MajorThis appears to regress the previously agreed JJWT BOM upgrade.
Lines 6-8 still hard-pin
0.12.3on each JJWT artifact. The PR checklist says this was supposed to move tojjwt-bom:0.13.0, so the build is back to manual version coordination and the three modules can drift again.📦 Proposed Gradle change
- implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0') + implementation 'io.jsonwebtoken:jjwt-api' + runtimeOnly 'io.jsonwebtoken:jjwt-impl' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/build.gradle` around lines 6 - 8, The build currently hard-pins io.jsonwebtoken artifacts ('io.jsonwebtoken:jjwt-api', 'io.jsonwebtoken:jjwt-impl', 'io.jsonwebtoken:jjwt-jackson') to 0.12.3 which regresses the agreed BOM upgrade; replace the explicit version pins by importing the jjwt BOM (use 'io.jsonwebtoken:jjwt-bom:0.13.0') via Gradle's platform/dependencyManagement and remove the :0.12.3 suffixes from those three dependency declarations so their versions are managed by jjwt-bom:0.13.0.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (2)
79-92:⚠️ Potential issue | 🟠 MajorRace condition on registration remains unaddressed.
The check-then-save pattern (
findByEmail().isPresent()followed bysave()) is not atomic. Concurrent requests can both pass the check and attempt to save, causing one to fail with an unhandled exception. Remove the pre-check and catchDataIntegrityViolationExceptioninstead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 79 - 92, In AuthService.register replace the non-atomic check-then-save (the call to repository.findByEmail(...) and the boolean exists branch) with a single save attempt and handle concurrent-unique constraint failures by catching DataIntegrityViolationException around repository.save(userEntity); on catch rethrow a clear IllegalStateException("The account exists, try login, please") (or map to your service-level error) so concurrent registrations don't let two requests pass the pre-check and cause an unhandled DB exception; keep construction of UserEntity and passwordEncoder.encode(...) as-is and only remove the pre-check and add the try/catch around repository.save.
54-60:⚠️ Potential issue | 🔴 CriticalAccount enumeration vulnerability remains unaddressed.
Different error messages for "user not found" vs "invalid password" allow attackers to enumerate valid accounts. Use a single generic message for both cases.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 54 - 60, The login(LoginRequest) method currently throws distinct exceptions for missing user (repository.findByEmail) and wrong password (passwordEncoder.matches), enabling account enumeration; change it to always respond with the same generic error message by: after calling repository.findByEmail(request.email()) use a constant-time flow—retrieve the UserEntity if present or a dummy UserEntity/dummy password hash and always call passwordEncoder.matches(request.password(), <actualOrDummyHash>), then throw the same generic exception message (e.g., "Invalid email or password") on any failure instead of different messages from findByEmail or matches; update the thrown exception text used in login(...) accordingly to reference the unified message.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
18-24: 🧹 Nitpick | 🔵 TrivialUse Lombok annotations for logger and constructor.
Replace manual logger and constructor with
@Slf4jand@RequiredArgsConstructor.Suggested fix
+import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; `@RestController` `@RequestMapping`("/api/auth") +@Slf4j +@RequiredArgsConstructor public class AuthController { - private static final Logger log = LoggerFactory.getLogger(AuthController.class); - private final AuthService authService; - - public AuthController(AuthService authService) { - this.authService = authService; - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 18 - 24, Replace the manual logger and constructor in AuthController with Lombok annotations: remove the line declaring "private static final Logger log = LoggerFactory.getLogger(AuthController.class);" and delete the explicit constructor "public AuthController(AuthService authService) { this.authService = authService; }", then annotate the class with `@Slf4j` and `@RequiredArgsConstructor` and ensure the existing "private final AuthService authService;" remains; add the Lombok imports for lombok.RequiredArgsConstructor and lombok.extern.slf4j.Slf4j if not present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java`:
- Around line 21-44: The file has Spotless formatting violations; run the
formatter and commit the changes: run ./gradlew spotlessApply and reformat
ApplicationConfig.java, ensuring methods userDetailsService,
authenticationProvider, authenticationManager, and passwordEncoder adhere to
project formatting (spacing, imports, braces) so Spotless passes; then add and
commit the modified file.
- Around line 22-26: The ApplicationConfig defines an inline UserDetailsService
bean via the userDetailsService() method which duplicates the existing
CustomUserDetailsService (`@Service`) and can cause
NoUniqueBeanDefinitionException; remove the userDetailsService() `@Bean` method
and instead have ApplicationConfig accept and use CustomUserDetailsService via
constructor injection (or inject it where needed) so there is a single
UserDetailsService implementation; ensure any references to userRepository in
this class are updated to use the injected CustomUserDetailsService.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 18-25: The file has Spotless formatting violations in
CustomUserDetailsService; run ./gradlew spotlessApply to auto-format, then
commit the changes; ensure the method loadUserByUsername, the
UserDetailsImpl.build(user) call, and surrounding whitespace/indentation for
UserEntity user = userRepository.findByEmail(...) remain intact after formatting
(no behavioral changes), and re-run CI to confirm the Spotless check passes.
- Around line 13-17: Replace the explicit constructor in
CustomUserDetailsService with Lombok's `@RequiredArgsConstructor`: remove the
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository; } constructor, annotate the
CustomUserDetailsService class with `@RequiredArgsConstructor`, keep
userRepository as a final field, and ensure Lombok's import
(lombok.RequiredArgsConstructor) is present so constructor injection is handled
automatically.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java`:
- Line 33: Remove the dead commented-out matcher in SecurityConfig.java that
reads ".requestMatchers(\"/secured/**\").authenticated().rermitAll()"—delete the
entire commented line so the typo "rermitAll" and the unused config are gone;
update the commit to remove this comment to resolve the Spotless failure and
keep the SecurityConfig class clean.
- Around line 30-34: The security rule requiring hasRole("ADMIN") is unreachable
because Role.java only defines STUDENT and TEACHER and
UserEntity#getAuthorities() yields ROLE_STUDENT/ROLE_TEACHER; to fix, either add
an ADMIN entry to the Role enum (and ensure any user that should be admin is
created with Role.ADMIN so UserEntity#getAuthorities() emits ROLE_ADMIN) or
change the rule in SecurityConfig (the
.requestMatchers("/api/admin/**").hasRole("ADMIN") call) to one of the existing
roles (for example hasRole("TEACHER") or hasAnyRole("TEACHER","STUDENT") as
appropriate) so the matcher can be satisfied by current users.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Around line 42-47: The getNewToken handler currently returns 201 Created but
should return 200 OK since /refresh isn't creating a resource; update
AuthController.getNewToken to return
ResponseEntity.ok(authService.getNewToken(tokenDto)) (use ResponseEntity.ok(...)
instead of ResponseEntity.status(201).body(...)) so the method returns a 200 OK
with the AuthResponse.
- Line 39: Replace the magic numeric status code in AuthController's return
statement: change the ResponseEntity.status(201) call (in the method returning
authService.register(request)) to ResponseEntity.status(HttpStatus.CREATED) and
ensure HttpStatus is imported; this improves readability and uses the named
constant instead of the literal 201.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 4-5: Replace the manual SLF4J logger with Lombok's `@Slf4j` on the
MainController class: remove the import statements for org.slf4j.Logger and
LoggerFactory and delete the Logger field declaration (the manual logger
variable), then annotate the MainController class with `@Slf4j` so you can use the
generated 'log' instance in methods; ensure Lombok is enabled in the project
build if not already.
- Line 29: The `@GetMapping` annotation has an extra space before the parentheses
(`@GetMapping ("/hello")`); remove the space so it reads `@GetMapping("/hello")`
to match the project's annotation formatting (consistent with other annotations
in this class) — update the annotation above the corresponding controller method
to eliminate the stray space.
- Line 31: The response string in MainController.java is truncated: replace the
return value "You are auth" with a full, user-friendly phrase like "You are
authenticated" in the method that currently returns that string (locate the
controller method in class MainController that returns the authentication
message) so the endpoint returns a clear, grammatically correct message.
- Line 25: The log call in MainController that currently uses log.error("Call
userAccess") is using the wrong severity; change it to a non-error level such as
log.info(...) or log.debug(...) (choose info for normal operational tracing or
debug for verbose dev tracing) so the UserAccess invocation is not logged as an
error; update the statement in the MainController class where "Call userAccess"
is logged.
- Line 20: Remove the debug System.out.println(principal) call from
MainController (the print occurs where principal is logged) and either delete
the line or replace it with a proper logging call via the class logger (e.g.,
logger.debug/trace with contextual message) so the application uses the
configured logging framework instead of System.out.
- Around line 18-27: Remove the redundant null-check in the
MainController.userAccess(Principal principal) method (Principal will never be
null for this secured endpoint) and always return principal.getName(); also
change the logging call from log.error("Call userAccess") to a non-error level
such as log.info(...) or log.debug(...) to reflect successful access.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java`:
- Around line 3-5: The TokenDto record uses snake_case and has no validation;
change the record component name from refresh_token to refreshToken, annotate it
with a validation constraint such as `@NotBlank` (or `@NotNull/`@NotEmpty per your
needs) to enforce non-empty input, and add `@JsonProperty`("refresh_token") on the
component so incoming JSON still binds correctly; update any callsites (e.g.,
AuthService.refresh_token() usages) to use the new accessor name refreshToken()
if necessary.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Around line 10-11: The repository method names and parameter names should
match what they query: rename findByEmail(String login) to findByEmail(String
email) and rename findUserEntitiesByUsername(String username) to
findByUsername(String username) in the UserRepository interface (return types
remain Optional<UserEntity>); then update all call sites that reference the old
names/parameter to use the new method names/parameter names (e.g., auth/login
code) to avoid broken references and keep naming consistent.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 39-61: Wrap the JWT parsing and validation logic in
JwtAuthenticationFilter#doFilterInternal (the block that calls
jwtService.extractUsername(jwt),
customUserDetailsService.loadUserByUsername(...) and
jwtService.isTokenValid(...)) in a try-catch that catches JWT-related exceptions
(e.g., JwtException, IllegalArgumentException) and handles them by logging via
the class logger (remove System.out.println) and then calling
filterChain.doFilter(request, response) and returning so the request proceeds
without setting authentication; do not let the exception propagate as a 500.
Ensure the catch does not swallow other unexpected exceptions and only bypasses
authentication-related errors.
- Line 42: Remove the debug System.out.println in JwtAuthenticationFilter (the
"System.out.println(\"Email: \" + email)" call); either delete it or replace it
with SLF4J logging by adding a private static final Logger (e.g., log) via
LoggerFactory.getLogger(JwtAuthenticationFilter.class) and use
log.debug/log.info with a parameterized message (e.g., log.debug("Email: {}",
email)) so no println remains in production code.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java`:
- Around line 40-44: The generateRefreshToken method creates a new SecureRandom
on every call which is inefficient; make a single SecureRandom instance at the
class level (e.g., a private final SecureRandom secureRandom or static final
SecureRandom) and use that instance inside generateRefreshToken to fill
randomBytes, ensuring thread-safety if needed (use ThreadLocalRandom or a static
SecureRandom is acceptable for this usage) and replace new SecureRandom() in
generateRefreshToken with the class-level field.
- Around line 35-38: getSigningKey() decodes secretKey and constructs a
Keys.hmacShaKeyFor(...) on every invocation; cache the decoded Key in a private
field (e.g., signingKey) and initialize it once (either in a `@PostConstruct`
method in JwtService or via a thread-safe lazy init) so subsequent calls to
getSigningKey() return the cached signingKey instead of recreating it each time.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 4-6: The file UserDetailsImpl.java imports lombok.Data and
lombok.RequiredArgsConstructor but they are not used; remove the unused import
statements (lombok.Data and lombok.RequiredArgsConstructor) from the import
block and keep only the actually used lombok import(s) such as
lombok.AllArgsConstructor to clean up warnings and unused imports in the
UserDetailsImpl class.
- Around line 30-33: UserDetailsImpl.getAuthorities() currently returns an empty
list; update it to map the user's role into a GrantedAuthority (e.g.,
SimpleGrantedAuthority) so role-based checks work. Locate the UserDetailsImpl
class and its getAuthorities() method and return a collection built from the
instance's role field (or getRole()), converting the role name to the authority
string expected by Spring Security (prefixing with "ROLE_" if your app uses that
convention) and wrapping it in a SimpleGrantedAuthority (or another
GrantedAuthority implementation).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 70-72: The refresh token expiry is being set using
jwtService.getJwtExpiration() in AuthService (at the
refreshTokenEntity.setExpiresAt calls), which uses the access-token lifetime;
add and use a separate refresh token expiration value instead: introduce a new
configuration or service method (e.g., jwtService.getRefreshTokenExpiration() or
a config property refreshTokenExpiration) and replace the usages at the
refreshTokenEntity.setExpiresAt sites (the occurrences around lines 70–72,
103–105, 131–133) to call the new refresh-token expiration so refresh tokens
have a longer TTL than access tokens.
- Around line 33-35: The class injects unused dependencies authenticationManager
and userDetailsService; remove the fields private final AuthenticationManager
authenticationManager and private final UserDetailsService userDetailsService
and drop their parameters from the AuthService constructor (and any
corresponding assignments) so only jwtService remains injected; ensure imports
for those types are removed if now unused and update any constructor
calls/instantiations accordingly.
- Around line 112-142: The getNewToken method currently leaves the used
RefreshTokenEntity (refreshToken) valid, allowing refresh token replay; after
generating and saving the new RefreshTokenEntity (refreshTokenEntity) you should
invalidate the old token by removing or marking it revoked: call
refreshTokenRepository.delete(refreshToken) or set a revoked/expired flag on the
existing RefreshTokenEntity and save it, ensuring only the newly created token
(newRefreshToken) remains valid; update getNewToken to perform this invalidation
step immediately after refreshTokenRepository.save(refreshTokenEntity).
---
Duplicate comments:
In `@backend/smartjam-api/build.gradle`:
- Around line 6-8: The build currently hard-pins io.jsonwebtoken artifacts
('io.jsonwebtoken:jjwt-api', 'io.jsonwebtoken:jjwt-impl',
'io.jsonwebtoken:jjwt-jackson') to 0.12.3 which regresses the agreed BOM
upgrade; replace the explicit version pins by importing the jjwt BOM (use
'io.jsonwebtoken:jjwt-bom:0.13.0') via Gradle's platform/dependencyManagement
and remove the :0.12.3 suffixes from those three dependency declarations so
their versions are managed by jjwt-bom:0.13.0.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Around line 18-24: Replace the manual logger and constructor in AuthController
with Lombok annotations: remove the line declaring "private static final Logger
log = LoggerFactory.getLogger(AuthController.class);" and delete the explicit
constructor "public AuthController(AuthService authService) { this.authService =
authService; }", then annotate the class with `@Slf4j` and
`@RequiredArgsConstructor` and ensure the existing "private final AuthService
authService;" remains; add the Lombok imports for lombok.RequiredArgsConstructor
and lombok.extern.slf4j.Slf4j if not present.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 79-92: In AuthService.register replace the non-atomic
check-then-save (the call to repository.findByEmail(...) and the boolean exists
branch) with a single save attempt and handle concurrent-unique constraint
failures by catching DataIntegrityViolationException around
repository.save(userEntity); on catch rethrow a clear IllegalStateException("The
account exists, try login, please") (or map to your service-level error) so
concurrent registrations don't let two requests pass the pre-check and cause an
unhandled DB exception; keep construction of UserEntity and
passwordEncoder.encode(...) as-is and only remove the pre-check and add the
try/catch around repository.save.
- Around line 54-60: The login(LoginRequest) method currently throws distinct
exceptions for missing user (repository.findByEmail) and wrong password
(passwordEncoder.matches), enabling account enumeration; change it to always
respond with the same generic error message by: after calling
repository.findByEmail(request.email()) use a constant-time flow—retrieve the
UserEntity if present or a dummy UserEntity/dummy password hash and always call
passwordEncoder.matches(request.password(), <actualOrDummyHash>), then throw the
same generic exception message (e.g., "Invalid email or password") on any
failure instead of different messages from findByEmail or matches; update the
thrown exception text used in login(...) accordingly to reference the unified
message.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 84466cbb-0e49-460b-b76a-fbd3c651e5a9
📒 Files selected for processing (18)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/resources/application.yaml
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
Outdated
Show resolved
Hide resolved
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Outdated
Show resolved
Hide resolved
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (14)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java (1)
3-8: 🧹 Nitpick | 🔵 TrivialClarify the enum's purpose or rename it to reflect its usage.
The name
AvailabilityStatusis ambiguous in an authentication context—"availability" typically implies online/offline presence, not account state. The values (AVAILABLE,UNAVAILABLE,PENDING,BANNED) suggest this represents account/user status. Consider renaming toAccountStatusorUserStatus, and add a Javadoc comment explaining its intended use inAuthResponse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java` around lines 3 - 8, The enum AvailabilityStatus is ambiguous for authentication/account state; rename the enum to a clearer name like AccountStatus or UserStatus and update all references (e.g., in AuthResponse) accordingly, and add a Javadoc comment above the enum explaining that it represents account/user authentication status (e.g., ACTIVE/INACTIVE/PENDING/BANNED semantics) so its purpose is self-documenting.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java (2)
50-53: 🧹 Nitpick | 🔵 TrivialAdd audit timestamp fields.
Consider adding
createdAtandupdatedAtfields with@CreationTimestampand@UpdateTimestampannotations for audit tracking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java` around lines 50 - 53, Add audit timestamp fields to UserEntity by declaring two new fields (e.g., private OffsetDateTime createdAt and private OffsetDateTime updatedAt) annotated with `@CreationTimestamp` and `@UpdateTimestamp` respectively, add corresponding `@Column` annotations (nullable = false for createdAt, nullable = true/false per policy for updatedAt), import org.hibernate.annotations.CreationTimestamp and UpdateTimestamp and java.time.OffsetDateTime (or Instant), and generate getters/setters for createdAt and updatedAt so the persistence layer will populate them automatically.
26-28: 🧹 Nitpick | 🔵 TrivialConsider using UUID for user identifiers.
Using
Longwith sequence generation can expose user enumeration vulnerabilities. UUIDs provide better security by making IDs non-sequential and unpredictable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java` around lines 26 - 28, Change the UserEntity id from a sequential Long to a UUID: update the field type in class UserEntity (the id field annotated with `@Id` and `@GeneratedValue`) to java.util.UUID, configure a UUID generator (e.g., add a `@GenericGenerator`(name="UUID", strategy="org.hibernate.id.UUIDGenerator") and use `@GeneratedValue`(generator="UUID")), mark the column as non-nullable/ non-updatable, and adjust imports and any code references (repositories, DTOs, tests) that expect Long IDs to use UUID instead.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)
10-12: 🧹 Nitpick | 🔵 TrivialAlign method and parameter names with query semantics.
findByEmail(String login): Parameter should beloginfindUserEntitiesByUsername: Should befindByUsername(Spring Data convention; "Entities" plural is unnecessary forOptional<UserEntity>)Suggested fix
public interface UserRepository extends JpaRepository<UserEntity, Long> { - Optional<UserEntity> findByEmail(String login); + Optional<UserEntity> findByEmail(String email); - Optional<UserEntity> findUserEntitiesByUsername(String username); + Optional<UserEntity> findByUsername(String username); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` around lines 10 - 12, The repository methods have mismatched names/params: change Optional<UserEntity> findByEmail(String login) to use a parameter named email (e.g., findByEmail(String email)) and rename Optional<UserEntity> findUserEntitiesByUsername(String username) to the Spring Data convention findByUsername(String username); update any callers/usages to the new method names/parameter name to keep signatures consistent with query semantics (refer to findByEmail and findUserEntitiesByUsername to locate the declarations).backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd validation and use Java naming conventions.
This record lacks field-level validation constraints, making
@Validin the controller ineffective. The snake_case field name (refresh_token) is non-idiomatic Java. Use camelCase with@JsonPropertyfor JSON mapping.Suggested fix
package com.smartjam.smartjamapi.dto; -public record TokenDto(String refresh_token) {} +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; + +public record TokenDto( + `@NotBlank` + `@JsonProperty`("refresh_token") + String refreshToken +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java` at line 3, Update the TokenDto record to follow Java naming conventions and add validation: rename the component from refresh_token to refreshToken in the TokenDto record signature, annotate the parameter with validation annotations such as `@NotBlank` (or appropriate javax/jakarta.validation constraint) to make controller-level `@Valid` effective, and add `@JsonProperty`("refresh_token") to preserve the incoming JSON mapping; ensure imports for `@JsonProperty` and the chosen validation annotation are added and referenced on the TokenDto record component.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd validation constraints to enable request validation.
The
@Validannotation inAuthControllerrequires field-level constraints on this DTO to be effective. Without them, empty or malformed credentials are accepted.Suggested fix
package com.smartjam.smartjamapi.dto; -public record LoginRequest(String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record LoginRequest( + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8, max = 72) String password +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java` at line 3, The LoginRequest record lacks validation annotations, so add Jakarta Bean Validation constraints to its components: annotate the email component with `@NotBlank` and `@Email` and the password component with `@NotBlank` (optionally `@Size`(min=8) or other policy); update imports to use jakarta.validation.constraints.* so the `@Valid` in AuthController actually enforces these rules. Locate the record declaration LoginRequest(String email, String password) and apply the annotations to the email and password components accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java (1)
20-32: 🛠️ Refactor suggestion | 🟠 MajorUse
CustomUserDetailsServiceas the singleUserDetailsServicesource.This file creates a second user-loading implementation even though
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javaalready provides one. Keeping both makes the DAO provider and JWT path easy to drift out of sync.Suggested patch
- private final UserRepository userRepository; + private final CustomUserDetailsService customUserDetailsService; - - `@Bean` - public UserDetailsService userDetailsService() { - return email -> - userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Email not found")); - } `@Bean` public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService()); + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(customUserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java` around lines 20 - 32, The file defines a local UserDetailsService bean (userDetailsService()) which duplicates the existing CustomUserDetailsService and risks divergence; replace use of the local lambda by wiring the existing CustomUserDetailsService into the authentication provider: remove or stop exposing the userDetailsService() method and have authenticationProvider() use the CustomUserDetailsService bean (inject or reference CustomUserDetailsService) when constructing the DaoAuthenticationProvider, while keeping the passwordEncoder() call intact so DaoAuthenticationProvider.setPasswordEncoder(passwordEncoder()) remains unchanged.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (4)
78-91:⚠️ Potential issue | 🟠 MajorRemove the check-then-save race from registration.
Lines 79-91 are not atomic: two concurrent requests can both pass
isPresent()and then race intosave(). Let the database enforce uniqueness and translateDataIntegrityViolationExceptioninto the existing domain error.Suggested patch
+import org.springframework.dao.DataIntegrityViolationException; + public AuthResponse register(RegisterRequest request) { - boolean exists = repository.findByEmail(request.email()).isPresent(); - - if (exists) { - throw new IllegalStateException("The account exists, try login, please"); - } - UserEntity userEntity = new UserEntity(); userEntity.setUsername(request.username()); userEntity.setEmail(request.email()); userEntity.setPasswordHash(passwordEncoder.encode(request.password())); userEntity.setRole(Role.STUDENT); - repository.save(userEntity); + try { + repository.save(userEntity); + } catch (DataIntegrityViolationException ex) { + throw new IllegalStateException("The account exists, try login, please"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 78 - 91, Remove the non-atomic check-then-save race in AuthService.register by deleting the pre-check using repository.findByEmail(...) and instead wrap the repository.save(userEntity) call in a try/catch that catches DataIntegrityViolationException (or the specific persistence exception your stack throws) and rethrows the existing IllegalStateException("The account exists, try login, please") so uniqueness is enforced by the DB; keep setting username/email/passwordHash/role and passwordEncoder.encode(...) as-is and locate this logic in the register method to implement the change.
55-60:⚠️ Potential issue | 🟠 MajorReturn the same failure path for unknown emails and bad passwords.
Line 58 and Line 60 currently expose different auth failures, which enables account enumeration. Use one exception type and one generic message for both branches.
Suggested patch
UserEntity userEntity = repository .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + .orElseThrow(() -> new IllegalStateException("Invalid email or password")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { - throw new IllegalStateException("Invalid password"); + throw new IllegalStateException("Invalid email or password"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 55 - 60, AuthService.login currently throws different exceptions/messages for missing users (NoSuchElementException/"Login not found...") and bad passwords (IllegalStateException/"Invalid password"); change both branches to throw the same exception type with one generic message (e.g., "Invalid email or password") so repository.findByEmail(...).orElseThrow(...) and the password check (passwordEncoder.matches(...)) use the same exception and message to prevent account enumeration.
68-71:⚠️ Potential issue | 🟠 MajorGive refresh tokens their own lifetime.
Lines 71, 101, and 128 all reuse
jwtService.getJwtExpiration(), which is the access-token TTL used byJwtService.generateAccessToken(). Refresh tokens should use a separate, longer expiration value.Also applies to: 98-101, 125-128
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 68 - 71, The refresh token expiry is incorrectly using jwtService.getJwtExpiration() (the access-token TTL); change the refresh-token logic to use a separate, longer TTL by adding or using a dedicated expiration value (e.g., jwtService.getRefreshTokenExpiration() or a config property) and replace calls where RefreshTokenEntity.setExpiresAt(...) uses jwtService.getJwtExpiration() (the occurrences around creation and updates of RefreshTokenEntity) so all refresh-token expiry assignments use the new refresh-token TTL instead of the access-token TTL.
109-130:⚠️ Potential issue | 🟠 MajorRevoke the previous refresh token during rotation.
Lines 110-130 mint a new refresh token but leave the old row valid. That allows refresh-token replay until expiration; delete or mark the old token revoked once the replacement has been persisted.
Suggested patch
refreshTokenRepository.save(refreshTokenEntity); + refreshTokenRepository.delete(refreshToken); return new AuthResponse( "Token generate successfully", AvailabilityStatus.AVAILABLE, newRefreshToken, accessToken);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 109 - 130, getNewToken currently creates and saves a new RefreshTokenEntity but leaves the previous refreshToken row valid, which permits replay; update getNewToken to revoke the old token (e.g., call refreshTokenRepository.delete(refreshToken) or set a "revoked"/"revokedAt" field on the existing refreshToken entity and save it) immediately after persisting the new RefreshTokenEntity, and ensure the operation is within the same transactional context so rotation is atomic; reference methods/classes: getNewToken, refreshTokenRepository, RefreshTokenEntity, jwtService, repository (for userEntity).backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
40-57:⚠️ Potential issue | 🟠 MajorTreat invalid bearer tokens as unauthenticated, not as 500s.
Lines 41-57 can throw on malformed, expired, or tampered JWTs, and Line 43 also writes the email to stdout. Catch only JWT/authentication exceptions, log them through SLF4J, and continue the chain without setting the security context.
Suggested patch
+import io.jsonwebtoken.JwtException; import com.smartjam.smartjamapi.config.CustomUserDetailsService; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; `@Component` +@Slf4j `@RequiredArgsConstructor` public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ - final String jwt = authHeader.substring(7); - final String email = jwtService.extractUsername(jwt); - - System.out.println("Email: " + email); - - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); - - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authToken); + final String jwt = authHeader.substring(7); + try { + final String email = jwtService.extractUsername(jwt); + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authToken); + } } + } catch (JwtException | IllegalArgumentException | UsernameNotFoundException ex) { + log.debug("Ignoring invalid bearer token", ex); } filterChain.doFilter(request, response);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 40 - 57, The filter JwtAuthenticationFilter currently prints the extracted email to stdout and lets exceptions from jwtService.extractUsername, customUserDetailsService.loadUserByUsername, or jwtService.isTokenValid propagate, causing 500s; replace the System.out.println with SLF4J logging and wrap the JWT extraction/validation block (the calls to jwtService.extractUsername, loadUserByUsername, and jwtService.isTokenValid) in a try-catch that catches JWT/authentication-related exceptions (e.g., token parsing, expired, signature exceptions) only, log the exception via the class logger at debug/warn, and on exception simply do not set SecurityContextHolder and continue filterChain.doFilter(request, response) so the request is treated as unauthenticated.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd Bean Validation annotations to the record components.
Without component constraints,
@Validdoes not reject blank usernames, malformed emails, or short passwords here. Apply the same pattern toLoginRequesttoo so auth input is rejected before it reaches the service layer.Suggested patch
package com.smartjam.smartjamapi.dto; -public record RegisterRequest(String username, String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record RegisterRequest( + `@NotBlank` String username, + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8, max = 72) String password) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java` at line 3, RegisterRequest's record components lack Bean Validation, so add annotations to each component: annotate username with `@NotBlank`, email with `@NotBlank` and `@Email`, and password with `@NotBlank` and `@Size`(min = 8) (imports from jakarta.validation.constraints.* or your project’s validation package). Update the RegisterRequest record declaration to include these annotations on the components (e.g., public record RegisterRequest(`@NotBlank` String username, `@NotBlank` `@Email` String email, `@NotBlank` `@Size`(min=8) String password) {}), and apply the same pattern to the LoginRequest record (annotate its username/email and password components similarly) so `@Valid` will enforce constraints before service-layer handling.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
23-25:⚠️ Potential issue | 🟠 MajorPopulate authorities from the user's role.
Line 25 always returns an empty collection, but
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javaand method security depend on authorities, andJwtService.generateAccessToken()serializes them into the JWT. WithList.of()here, every authenticated user has no roles.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 23 - 25, getAuthorities in UserDetailsImpl currently returns an empty list causing no roles to be propagated into SecurityConfig and JwtService.generateAccessToken; update UserDetailsImpl.getAuthorities() to build and return a collection of GrantedAuthority derived from the user's role (e.g., map the user's role/name via new SimpleGrantedAuthority("ROLE_" + roleName) or appropriate string) so authorities reflect the stored user role and are serialized into the JWT and used by method security.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java`:
- Line 5: Rename the record components refresh_token and access_token to
camelCase (refreshToken, accessToken) in the AuthResponse record and annotate
them with `@JsonProperty`("refresh_token") and `@JsonProperty`("access_token")
respectively to preserve JSON names; update the AuthResponse declaration
accordingly and add the import for com.fasterxml.jackson.annotation.JsonProperty
so serialization keeps the original snake_case keys while the Java API uses
idiomatic camelCase.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Around line 14-20: The RefreshTokenEntity currently stores tokens in plaintext
in the token field; change storage to a hashed value by renaming or adding a
tokenHash field and persist only a secure hash (e.g., SHA-256 or PBKDF2/HMAC
with a server-side secret/salt) instead of the raw token in RefreshTokenEntity,
update all creation paths that call whatever method issues refresh tokens to
store the hash of the generated token (and return the raw token only to the
client), and update validation logic that currently compares raw token values to
instead hash the presented token and compare against the stored tokenHash
(ensure any repository methods using token lookups are adjusted to query by
hash). Ensure hashing uses a cryptographically secure algorithm and consistent
salt/secret handling and update entity column annotations on the new tokenHash
field accordingly.
- Around line 10-14: The RefreshTokenEntity JPA entity is missing a no-argument
constructor; add Lombok's `@NoArgsConstructor` to the class declaration (alongside
the existing `@Entity`, `@Table`, `@Setter`, `@Getter` on RefreshTokenEntity) and import
lombok.NoArgsConstructor so the JPA provider can instantiate the entity via a
default constructor.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Around line 55-63: UserDetailsImpl.build() currently doesn't copy role
authorities from UserEntity, causing getAuthorities() to return empty; update
UserDetailsImpl.build(UserEntity user) to set the authorities field from
user.getAuthorities() (or map user.getRole()/role.name() into
SimpleGrantedAuthority if build currently only takes role) so the returned
UserDetailsImpl contains the same collection as UserEntity.getAuthorities(), and
ensure password is set from user.getPassword()/getPasswordHash(); additionally,
since UserEntity is only a JPA entity and not used as a UserDetails
implementation, remove the implements UserDetails from the UserEntity class and
any unused UserDetails method implementations (e.g., getAuthorities(),
getPassword()) to eliminate dead code.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Line 22: In GlobalExceptionHandler, rename the exception handler methods from
the noun form to verb form (e.g., change handlerGenericException to
handleGenericException) and apply the same pattern to the other methods flagged
(the methods at the other locations that start with "handler" — rename them to
"handle..." equivalents) so method names are verbs; update any internal
references/annotations that refer to the old method names to use the new names
(e.g., in GlobalExceptionHandler, update handlerGenericException →
handleGenericException and the other "handler*" methods accordingly).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`:
- Around line 8-10: Add token lifecycle methods to RefreshTokenRepository:
declare a simple derived query method Optional/void deleteByUser(UserEntity
user) (or void deleteAllByUser(UserEntity user)) to support logout/revocation,
and add a bulk removal method for expired tokens using `@Modifying` `@Transactional`
with either a derived query like void deleteAllByExpiryDateBefore(Instant
cutoff) or a `@Modifying` `@Query`("delete from RefreshTokenEntity r where
r.expiryDate < :cutoff") void deleteExpiredSince(`@Param`("cutoff") Instant
cutoff); reference RefreshTokenRepository, RefreshTokenEntity, and UserEntity
when adding these signatures.
---
Duplicate comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java`:
- Around line 20-32: The file defines a local UserDetailsService bean
(userDetailsService()) which duplicates the existing CustomUserDetailsService
and risks divergence; replace use of the local lambda by wiring the existing
CustomUserDetailsService into the authentication provider: remove or stop
exposing the userDetailsService() method and have authenticationProvider() use
the CustomUserDetailsService bean (inject or reference CustomUserDetailsService)
when constructing the DaoAuthenticationProvider, while keeping the
passwordEncoder() call intact so
DaoAuthenticationProvider.setPasswordEncoder(passwordEncoder()) remains
unchanged.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java`:
- Line 3: The LoginRequest record lacks validation annotations, so add Jakarta
Bean Validation constraints to its components: annotate the email component with
`@NotBlank` and `@Email` and the password component with `@NotBlank` (optionally
`@Size`(min=8) or other policy); update imports to use
jakarta.validation.constraints.* so the `@Valid` in AuthController actually
enforces these rules. Locate the record declaration LoginRequest(String email,
String password) and apply the annotations to the email and password components
accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`:
- Line 3: RegisterRequest's record components lack Bean Validation, so add
annotations to each component: annotate username with `@NotBlank`, email with
`@NotBlank` and `@Email`, and password with `@NotBlank` and `@Size`(min = 8) (imports
from jakarta.validation.constraints.* or your project’s validation package).
Update the RegisterRequest record declaration to include these annotations on
the components (e.g., public record RegisterRequest(`@NotBlank` String username,
`@NotBlank` `@Email` String email, `@NotBlank` `@Size`(min=8) String password) {}), and
apply the same pattern to the LoginRequest record (annotate its username/email
and password components similarly) so `@Valid` will enforce constraints before
service-layer handling.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java`:
- Line 3: Update the TokenDto record to follow Java naming conventions and add
validation: rename the component from refresh_token to refreshToken in the
TokenDto record signature, annotate the parameter with validation annotations
such as `@NotBlank` (or appropriate javax/jakarta.validation constraint) to make
controller-level `@Valid` effective, and add `@JsonProperty`("refresh_token") to
preserve the incoming JSON mapping; ensure imports for `@JsonProperty` and the
chosen validation annotation are added and referenced on the TokenDto record
component.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Around line 50-53: Add audit timestamp fields to UserEntity by declaring two
new fields (e.g., private OffsetDateTime createdAt and private OffsetDateTime
updatedAt) annotated with `@CreationTimestamp` and `@UpdateTimestamp` respectively,
add corresponding `@Column` annotations (nullable = false for createdAt, nullable
= true/false per policy for updatedAt), import
org.hibernate.annotations.CreationTimestamp and UpdateTimestamp and
java.time.OffsetDateTime (or Instant), and generate getters/setters for
createdAt and updatedAt so the persistence layer will populate them
automatically.
- Around line 26-28: Change the UserEntity id from a sequential Long to a UUID:
update the field type in class UserEntity (the id field annotated with `@Id` and
`@GeneratedValue`) to java.util.UUID, configure a UUID generator (e.g., add a
`@GenericGenerator`(name="UUID", strategy="org.hibernate.id.UUIDGenerator") and
use `@GeneratedValue`(generator="UUID")), mark the column as non-nullable/
non-updatable, and adjust imports and any code references (repositories, DTOs,
tests) that expect Long IDs to use UUID instead.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java`:
- Around line 3-8: The enum AvailabilityStatus is ambiguous for
authentication/account state; rename the enum to a clearer name like
AccountStatus or UserStatus and update all references (e.g., in AuthResponse)
accordingly, and add a Javadoc comment above the enum explaining that it
represents account/user authentication status (e.g.,
ACTIVE/INACTIVE/PENDING/BANNED semantics) so its purpose is self-documenting.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Around line 10-12: The repository methods have mismatched names/params: change
Optional<UserEntity> findByEmail(String login) to use a parameter named email
(e.g., findByEmail(String email)) and rename Optional<UserEntity>
findUserEntitiesByUsername(String username) to the Spring Data convention
findByUsername(String username); update any callers/usages to the new method
names/parameter name to keep signatures consistent with query semantics (refer
to findByEmail and findUserEntitiesByUsername to locate the declarations).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 40-57: The filter JwtAuthenticationFilter currently prints the
extracted email to stdout and lets exceptions from jwtService.extractUsername,
customUserDetailsService.loadUserByUsername, or jwtService.isTokenValid
propagate, causing 500s; replace the System.out.println with SLF4J logging and
wrap the JWT extraction/validation block (the calls to
jwtService.extractUsername, loadUserByUsername, and jwtService.isTokenValid) in
a try-catch that catches JWT/authentication-related exceptions (e.g., token
parsing, expired, signature exceptions) only, log the exception via the class
logger at debug/warn, and on exception simply do not set SecurityContextHolder
and continue filterChain.doFilter(request, response) so the request is treated
as unauthenticated.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 23-25: getAuthorities in UserDetailsImpl currently returns an
empty list causing no roles to be propagated into SecurityConfig and
JwtService.generateAccessToken; update UserDetailsImpl.getAuthorities() to build
and return a collection of GrantedAuthority derived from the user's role (e.g.,
map the user's role/name via new SimpleGrantedAuthority("ROLE_" + roleName) or
appropriate string) so authorities reflect the stored user role and are
serialized into the JWT and used by method security.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 78-91: Remove the non-atomic check-then-save race in
AuthService.register by deleting the pre-check using repository.findByEmail(...)
and instead wrap the repository.save(userEntity) call in a try/catch that
catches DataIntegrityViolationException (or the specific persistence exception
your stack throws) and rethrows the existing IllegalStateException("The account
exists, try login, please") so uniqueness is enforced by the DB; keep setting
username/email/passwordHash/role and passwordEncoder.encode(...) as-is and
locate this logic in the register method to implement the change.
- Around line 55-60: AuthService.login currently throws different
exceptions/messages for missing users (NoSuchElementException/"Login not
found...") and bad passwords (IllegalStateException/"Invalid password"); change
both branches to throw the same exception type with one generic message (e.g.,
"Invalid email or password") so repository.findByEmail(...).orElseThrow(...) and
the password check (passwordEncoder.matches(...)) use the same exception and
message to prevent account enumeration.
- Around line 68-71: The refresh token expiry is incorrectly using
jwtService.getJwtExpiration() (the access-token TTL); change the refresh-token
logic to use a separate, longer TTL by adding or using a dedicated expiration
value (e.g., jwtService.getRefreshTokenExpiration() or a config property) and
replace calls where RefreshTokenEntity.setExpiresAt(...) uses
jwtService.getJwtExpiration() (the occurrences around creation and updates of
RefreshTokenEntity) so all refresh-token expiry assignments use the new
refresh-token TTL instead of the access-token TTL.
- Around line 109-130: getNewToken currently creates and saves a new
RefreshTokenEntity but leaves the previous refreshToken row valid, which permits
replay; update getNewToken to revoke the old token (e.g., call
refreshTokenRepository.delete(refreshToken) or set a "revoked"/"revokedAt" field
on the existing refreshToken entity and save it) immediately after persisting
the new RefreshTokenEntity, and ensure the operation is within the same
transactional context so rotation is atomic; reference methods/classes:
getNewToken, refreshTokenRepository, RefreshTokenEntity, jwtService, repository
(for userEntity).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a88cbe45-832e-40fc-95ec-0bd5c0bab874
📒 Files selected for processing (20)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
Outdated
Show resolved
Hide resolved
| @Override | ||
| public Collection<? extends GrantedAuthority> getAuthorities() { | ||
| return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable String getPassword() { | ||
| return passwordHash; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how UserDetails is used across the codebase to understand which implementation is active
rg -n "UserDetails" backend/smartjam-api/src/main/java/ -A 2 -B 2Repository: Satlykovs/SmartJam
Length of output: 16021
🏁 Script executed:
cat -n backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javaRepository: Satlykovs/SmartJam
Length of output: 1904
🏁 Script executed:
cat -n backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javaRepository: Satlykovs/SmartJam
Length of output: 2104
🏁 Script executed:
# Search for places where UserEntity is passed to Spring Security methods
rg -n "UserEntity" backend/smartjam-api/src/main/java/ | grep -E "(Authentication|SecurityContext|UserDetails|getAuthorities)" -A 2 -B 2Repository: Satlykovs/SmartJam
Length of output: 1653
🏁 Script executed:
# Check if getAuthorities() from UserEntity is ever invoked
rg -n "getAuthorities" backend/smartjam-api/src/main/java/ -B 3 -A 3Repository: Satlykovs/SmartJam
Length of output: 3561
🏁 Script executed:
# Check if authorities are actually used in any authorization checks
rg -n "hasRole|hasAuthority|@Secured|@PreAuthorize" backend/smartjam-api/src/main/java/Repository: Satlykovs/SmartJam
Length of output: 193
UserDetailsImpl.build() must copy authorities from UserEntity.
Authorization is currently broken: UserDetailsImpl.getAuthorities() returns an empty list, so all authorization checks requiring roles (e.g., .hasRole("ADMIN")) will fail. The build() method at line 19-20 must be updated to copy the role-based authorities from UserEntity.
Additionally, since UserEntity is only used as a JPA entity and never as a UserDetails implementation, consider removing the UserDetails interface from UserEntity to eliminate dead code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`
around lines 55 - 63, UserDetailsImpl.build() currently doesn't copy role
authorities from UserEntity, causing getAuthorities() to return empty; update
UserDetailsImpl.build(UserEntity user) to set the authorities field from
user.getAuthorities() (or map user.getRole()/role.name() into
SimpleGrantedAuthority if build currently only takes role) so the returned
UserDetailsImpl contains the same collection as UserEntity.getAuthorities(), and
ensure password is set from user.getPassword()/getPasswordHash(); additionally,
since UserEntity is only a JPA entity and not used as a UserDetails
implementation, remove the implements UserDetails from the UserEntity class and
any unused UserDetails method implementations (e.g., getAuthorities(),
getPassword()) to eliminate dead code.
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Show resolved
Hide resolved
...d/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
Outdated
Show resolved
Hide resolved
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (9)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (2)
35-38:⚠️ Potential issue | 🟡 MinorReturn
200 OKfromgetNewToken().
/refreshis not creating a permanent resource, so201 Createdis the wrong status here.Suggested fix
- return ResponseEntity.status(201).body(authService.getNewToken(tokenDto)); + return ResponseEntity.ok(authService.getNewToken(tokenDto));For HTTP auth/token refresh endpoints that return new tokens for an existing session, should the response status be 200 OK or 201 Created?Based on learnings: In SmartJam, token endpoints like
/refreshshould return200 OK, not201 Created.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 35 - 38, The endpoint getNewToken in AuthController currently returns ResponseEntity.status(201).body(...), which is incorrect for a token refresh; change it to return HTTP 200 OK instead (use ResponseEntity.ok(...) or ResponseEntity.status(200).body(...)) so that AuthController.getNewToken(...) returns 200 with the body from authService.getNewToken(tokenDto).
29-32:⚠️ Potential issue | 🟡 MinorReplace the magic status code in
register().
201is the right status here, but hard-coding it makes the controller less clear thanHttpStatus.CREATED.Suggested fix
+import org.springframework.http.HttpStatus; ... - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));Spring Framework ResponseEntity: what is the recommended way to return HTTP 201 Created from a controller, and should ResponseEntity.status(HttpStatus.CREATED) be preferred over ResponseEntity.status(201)?Based on learnings: In SmartJam controllers, avoid numeric status literals and use typed
HttpStatusconstants.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 29 - 32, Replace the numeric literal 201 in the AuthController.register method with the HttpStatus enum constant to improve readability and consistency; update the ResponseEntity.status call in register() (the method in AuthController that returns ResponseEntity<AuthResponse>) to use HttpStatus.CREATED instead of the hard-coded 201.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (3)
62-67: 🧹 Nitpick | 🔵 TrivialConsider separate handler for
IllegalStateExceptionreturning 401.Grouping
IllegalStateExceptionwith bad request exceptions may mask authentication failures. IfIllegalStateExceptionis thrown for auth-related errors (e.g., invalid credentials), it should return401 Unauthorizedinstead of400 Bad Request.♻️ Proposed separation
`@ExceptionHandler`( exception = { IllegalArgumentException.class, - IllegalStateException.class, MethodArgumentNotValidException.class }) public ResponseEntity<ErrorResponseDto> handleBadRequest(Exception e) { // ... } + `@ExceptionHandler`(IllegalStateException.class) + public ResponseEntity<ErrorResponseDto> handleUnauthorized(IllegalStateException e) { + log.warn("Unauthorized: {}", e.getMessage()); + return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Authentication failed"); + }Note: This requires adding
UNAUTHORIZEDto theErrorCodeenum.,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 62 - 67, Currently GlobalExceptionHandler groups IllegalStateException with bad-request handlers; separate IllegalStateException into its own `@ExceptionHandler` method in GlobalExceptionHandler that returns HttpStatus.UNAUTHORIZED and uses ErrorCode.UNAUTHORIZED (add UNAUTHORIZED to the ErrorCode enum), remove IllegalStateException from the existing handler annotation list, and ensure the new handler constructs the same error response object as other handlers but sets status 401 and ErrorCode.UNAUTHORIZED so auth-related IllegalStateExceptions produce a 401 instead of 400.
29-32: 🧹 Nitpick | 🔵 TrivialExtract a private helper to reduce duplication.
All four handlers construct
ErrorResponseDtoidentically. A helper method would consolidate this logic.♻️ Proposed refactor
+ private ResponseEntity<ErrorResponseDto> buildResponse(HttpStatus status, ErrorCode code, String message) { + var errorDto = new ErrorResponseDto(code, message, LocalDateTime.now()); + return ResponseEntity.status(status).body(errorDto); + } `@ExceptionHandler`(Exception.class) public ResponseEntity<ErrorResponseDto> handleGenericException(Exception e) { log.error("Unexpected error: ", e); - var errorDto = new ErrorResponseDto( - ErrorCode.INTERNAL_SERVER_ERROR, - "Internal server error", - LocalDateTime.now()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto); + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error"); }Apply the same pattern to the other handlers.
,
Also applies to: 41-44, 53-56, 71-74
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 29 - 32, Extract a private helper in GlobalExceptionHandler to build the ErrorResponseDto and replace the duplicated constructions in each handler; for example add a private method (e.g., createErrorResponseDto() or buildErrorResponseDto(ErrorCode, String) if you want it reusable) that returns new ErrorResponseDto(ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error", LocalDateTime.now()), then have each handler call that helper instead of directly instantiating ErrorResponseDto (apply the same change where ErrorResponseDto is constructed in the other handlers).
26-26: 🛠️ Refactor suggestion | 🟠 MajorMethod names still use noun "handler" instead of verb "handle".
Three methods still have inconsistent naming compared to
handleNoHandlerFoundon line 50.- public ResponseEntity<ErrorResponseDto> handlerGenericException(Exception e) { + public ResponseEntity<ErrorResponseDto> handleGenericException(Exception e) {- public ResponseEntity<ErrorResponseDto> handlerEntityNotFound(EntityNotFoundException e) { + public ResponseEntity<ErrorResponseDto> handleEntityNotFound(EntityNotFoundException e) {- public ResponseEntity<ErrorResponseDto> handlerBadRequest(Exception e) { + public ResponseEntity<ErrorResponseDto> handleBadRequest(Exception e) {,
Also applies to: 38-38, 68-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` at line 26, The three exception handler methods currently named with the noun "handler" (e.g., handlerGenericException, handlerNotFoundException, handlerValidationException) should be renamed to use the verb "handle" (handleGenericException, handleNotFoundException, handleValidationException) to match handleNoHandlerFound; update the method names and any internal references (annotations, tests, callers, or method-level `@ExceptionHandler` mappings if they reference the method name) so signatures remain consistent and compilation succeeds.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java (1)
5-5: 🛠️ Refactor suggestion | 🟠 MajorRename
NON_FOUND_PAGEtoRESOURCE_NOT_FOUND.The constant name is grammatically incorrect and semantically unclear.
RESOURCE_NOT_FOUNDbetter conveys the 404 meaning and aligns with standard HTTP terminology.- NON_FOUND_PAGE, + RESOURCE_NOT_FOUND,,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java` at line 5, Rename the enum constant NON_FOUND_PAGE in ErrorCode to RESOURCE_NOT_FOUND: update the declaration in the ErrorCode enum and change all usages/reference sites (e.g., any switch/case, comparisons, serialization/deserialization, tests, or mapping logic) to use RESOURCE_NOT_FOUND so compilation and behavior remain consistent; if this enum is persisted or used in APIs, ensure any string-based consumers (JSON, DB values) are adjusted or mapped to the new name to avoid breaking changes.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
40-55:⚠️ Potential issue | 🟠 MajorCatch JWT parse/validation failures inside the filter.
jwtService.extractUsername(jwt)andjwtService.isTokenValid(jwt, userDetails)can throw for malformed, expired, or tampered tokens. In the current flow that escapesdoFilterInternal()as a 500 instead of leaving the request unauthenticated for Spring Security to reject with 401.Suggested fix
+import io.jsonwebtoken.JwtException; + final String jwt = authHeader.substring(7); - final String email = jwtService.extractUsername(jwt); - - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); - - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authToken); - } - } + try { + final String email = jwtService.extractUsername(jwt); + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + } catch (JwtException | IllegalArgumentException ex) { + SecurityContextHolder.clearContext(); + } filterChain.doFilter(request, response);Verification script
#!/bin/bash set -euo pipefail echo "JwtAuthenticationFilter token handling:" sed -n '33,60p' backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java echo echo "JwtService parser implementation:" sed -n '45,70p' backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 40 - 55, Wrap calls to jwtService.extractUsername(jwt) and jwtService.isTokenValid(jwt, userDetails) inside a try/catch in JwtAuthenticationFilter (doFilterInternal) so parsing/validation exceptions are caught, logged at debug/warn, and do not propagate; on exception simply skip creating the UsernamePasswordAuthenticationToken and proceed to filterChain.doFilter(request, response) leaving SecurityContextHolder unchanged so the request remains unauthenticated (allow Spring Security to return 401). Reference the methods jwtService.extractUsername, jwtService.isTokenValid, customUserDetailsService.loadUserByUsername, SecurityContextHolder.getContext().setAuthentication, and filterChain.doFilter while implementing the catch.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java (1)
1-29:⚠️ Potential issue | 🟡 MinorSpotlessCheck is still failing on this file.
Backend CI is already reporting formatting violations here, so this patchset will not merge cleanly until the file is auto-formatted.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java` around lines 1 - 29, Run the project's Spotless formatter on this file (e.g., mvn spotless:apply or ./gradlew spotlessApply) to resolve the formatting violations in CustomUserDetailsService (class CustomUserDetailsService and method loadUserByUsername); ensure imports, spacing, line endings and indentation match the project's style so the file passes SpotlessCheck, then re-stage the reformatted file and push the change.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java (1)
28-31:⚠️ Potential issue | 🟠 Major
/api/admin/**is currently unreachable.This matcher requires
ROLE_ADMIN, but the current role model does not mint that authority. Every request under/api/admin/**will be rejected until you either add an admin role end-to-end or gate the route with an existing role.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java` around lines 28 - 31, The /api/admin/** matcher is unreachable because SecurityConfig uses requestMatchers("/api/admin/**").hasRole("ADMIN") but no component currently grants the ROLE_ADMIN authority; either change the matcher to require an existing role (e.g., hasRole("USER") or hasAnyRole("EXISTING_ADMIN_ROLE")) or ensure the authentication layer issues ROLE_ADMIN. Update the role-granting code (e.g., the UserDetailsService/Jwt token claims or wherever authorities are minted) to add the "ROLE_ADMIN" authority if you intend real admin gating, or replace hasRole("ADMIN") in the authorizeHttpRequests configuration with a role name that your system already mints so requests to /api/admin/** are reachable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 12-28: Remove the duplicate UserDetailsService bean in
ApplicationConfig: delete or comment out the userDetailsService() `@Bean` method
so only the `@Service` CustomUserDetailsService remains; ensure AuthService and
DaoAuthenticationProvider use CustomUserDetailsService (which returns
UserDetailsImpl) and that no bean returns UserEntity from a UserDetailsService
lambda (i.e., eliminate ApplicationConfig.userDetailsService() that currently
returns UserEntity) to resolve the ambiguous/incorrect UserDetailsService
registration.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java`:
- Around line 1-12: ErrorResponseDto has Spotless formatting violations causing
CI to fail; run the formatter and commit the changes by executing ./gradlew
spotlessApply and then reformat the ErrorResponseDto record (and any other
modified files) so imports, spacing and braces conform to project formatting;
ensure the ErrorResponseDto record definition remains intact (ErrorResponseDto,
ErrorCode, message, errorTime) and re-run the build to confirm CI passes.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 11-12: Remove the unused imports and duplicate controller-advice
annotation: delete the imports for Logger and LoggerFactory and the import for
org.springframework.web.bind.annotation.ControllerAdvice since the class
GlobalExceptionHandler already uses Lombok's `@Slf4j` and the Spring annotation
`@RestControllerAdvice`; ensure only `@Slf4j` and `@RestControllerAdvice` remain
imported to avoid unused-import warnings and redundant annotations.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 1-57: The file JwtAuthenticationFilter.java fails Spotless
formatting; run the project formatter (e.g., mvn spotless:apply or your IDE's
project formatter configured with Spotless/Google style) and reformat the
JwtAuthenticationFilter class—particularly the doFilterInternal method and
imports—so it conforms to the project's Spotless rules; after reformatting,
re-run Spotless check to confirm the formatting violations are resolved before
committing.
---
Duplicate comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 1-29: Run the project's Spotless formatter on this file (e.g., mvn
spotless:apply or ./gradlew spotlessApply) to resolve the formatting violations
in CustomUserDetailsService (class CustomUserDetailsService and method
loadUserByUsername); ensure imports, spacing, line endings and indentation match
the project's style so the file passes SpotlessCheck, then re-stage the
reformatted file and push the change.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java`:
- Around line 28-31: The /api/admin/** matcher is unreachable because
SecurityConfig uses requestMatchers("/api/admin/**").hasRole("ADMIN") but no
component currently grants the ROLE_ADMIN authority; either change the matcher
to require an existing role (e.g., hasRole("USER") or
hasAnyRole("EXISTING_ADMIN_ROLE")) or ensure the authentication layer issues
ROLE_ADMIN. Update the role-granting code (e.g., the UserDetailsService/Jwt
token claims or wherever authorities are minted) to add the "ROLE_ADMIN"
authority if you intend real admin gating, or replace hasRole("ADMIN") in the
authorizeHttpRequests configuration with a role name that your system already
mints so requests to /api/admin/** are reachable.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Around line 35-38: The endpoint getNewToken in AuthController currently
returns ResponseEntity.status(201).body(...), which is incorrect for a token
refresh; change it to return HTTP 200 OK instead (use ResponseEntity.ok(...) or
ResponseEntity.status(200).body(...)) so that AuthController.getNewToken(...)
returns 200 with the body from authService.getNewToken(tokenDto).
- Around line 29-32: Replace the numeric literal 201 in the
AuthController.register method with the HttpStatus enum constant to improve
readability and consistency; update the ResponseEntity.status call in register()
(the method in AuthController that returns ResponseEntity<AuthResponse>) to use
HttpStatus.CREATED instead of the hard-coded 201.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java`:
- Line 5: Rename the enum constant NON_FOUND_PAGE in ErrorCode to
RESOURCE_NOT_FOUND: update the declaration in the ErrorCode enum and change all
usages/reference sites (e.g., any switch/case, comparisons,
serialization/deserialization, tests, or mapping logic) to use
RESOURCE_NOT_FOUND so compilation and behavior remain consistent; if this enum
is persisted or used in APIs, ensure any string-based consumers (JSON, DB
values) are adjusted or mapped to the new name to avoid breaking changes.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 62-67: Currently GlobalExceptionHandler groups
IllegalStateException with bad-request handlers; separate IllegalStateException
into its own `@ExceptionHandler` method in GlobalExceptionHandler that returns
HttpStatus.UNAUTHORIZED and uses ErrorCode.UNAUTHORIZED (add UNAUTHORIZED to the
ErrorCode enum), remove IllegalStateException from the existing handler
annotation list, and ensure the new handler constructs the same error response
object as other handlers but sets status 401 and ErrorCode.UNAUTHORIZED so
auth-related IllegalStateExceptions produce a 401 instead of 400.
- Around line 29-32: Extract a private helper in GlobalExceptionHandler to build
the ErrorResponseDto and replace the duplicated constructions in each handler;
for example add a private method (e.g., createErrorResponseDto() or
buildErrorResponseDto(ErrorCode, String) if you want it reusable) that returns
new ErrorResponseDto(ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error",
LocalDateTime.now()), then have each handler call that helper instead of
directly instantiating ErrorResponseDto (apply the same change where
ErrorResponseDto is constructed in the other handlers).
- Line 26: The three exception handler methods currently named with the noun
"handler" (e.g., handlerGenericException, handlerNotFoundException,
handlerValidationException) should be renamed to use the verb "handle"
(handleGenericException, handleNotFoundException, handleValidationException) to
match handleNoHandlerFound; update the method names and any internal references
(annotations, tests, callers, or method-level `@ExceptionHandler` mappings if they
reference the method name) so signatures remain consistent and compilation
succeeds.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 40-55: Wrap calls to jwtService.extractUsername(jwt) and
jwtService.isTokenValid(jwt, userDetails) inside a try/catch in
JwtAuthenticationFilter (doFilterInternal) so parsing/validation exceptions are
caught, logged at debug/warn, and do not propagate; on exception simply skip
creating the UsernamePasswordAuthenticationToken and proceed to
filterChain.doFilter(request, response) leaving SecurityContextHolder unchanged
so the request remains unauthenticated (allow Spring Security to return 401).
Reference the methods jwtService.extractUsername, jwtService.isTokenValid,
customUserDetailsService.loadUserByUsername,
SecurityContextHolder.getContext().setAuthentication, and filterChain.doFilter
while implementing the catch.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3a990007-7732-4905-9fda-59cef9d50caf
📒 Files selected for processing (7)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
Outdated
Show resolved
Hide resolved
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Outdated
Show resolved
Hide resolved
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
Show resolved
Hide resolved
5cffcdb to
64664f7
Compare
No comments
There was a problem hiding this comment.
Actionable comments posted: 14
♻️ Duplicate comments (24)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (3)
5-6: 🧹 Nitpick | 🔵 TrivialUse Lombok
@Slf4jto align with controller conventions.Line [5]-Line [6] and Line [15] manually wire SLF4J, while
AuthControlleralready uses@Slf4j. Please standardize this controller too.Proposed change
import java.security.Principal; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ `@RestController` `@RequestMapping`("/secured") +@Slf4j public class MainController { - - private static final Logger log = LoggerFactory.getLogger(MainController.class);Also applies to: 11-16
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 5 - 6, Replace the manual SLF4J wiring in MainController (remove the imports org.slf4j.Logger and org.slf4j.LoggerFactory and the private static final Logger field) and annotate the class with Lombok's `@Slf4j` so logging matches AuthController; ensure lombok.slf4j.Slf4j is imported or available and update any logger usage to refer to the generated "log" instance inside MainController.
30-30:⚠️ Potential issue | 🟡 MinorFix truncated response text.
Line [30] returns an incomplete message (
"You are auth"). Use a clear phrase (for example,"You are authenticated").🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` at line 30, Replace the truncated response string in MainController by updating the return value that currently reads "You are auth" to a clear, complete phrase such as "You are authenticated"; locate the return statement inside the MainController class (the controller method returning the auth message) and modify the literal to the full message so API responses are readable and unambiguous.
19-25:⚠️ Potential issue | 🟡 MinorClean up
userAccessdiagnostics and verify the null-branch contract.Line [19] should not use
System.out.printlnin controller code, and Line [24] should not log a successful call aserror.
Also, if/secured/**is authenticated by security config, Line [20]-Line [22] is dead code.#!/bin/bash # Verify whether /secured endpoints always require authentication fd SecurityConfig.java backend/smartjam-api/src/main/java --exec sed -n '1,240p' {} rg -n -C3 'requestMatchers|anyRequest|authenticated|permitAll|/secured' --type java🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 19 - 25, In MainController.userAccess, remove the System.out.println(principal) diagnostic and replace the log.error("Call userAccess") with an appropriate level (e.g., log.info or log.debug) to reflect a successful call; then verify the security contract in SecurityConfig (look for requestMatchers/anyRequest/authenticated for /secured/**) and if those endpoints are guaranteed authenticated, remove the null-principal branch (the if (principal == null) return "Anonymous";) or update it to throw/return an explicit error if you prefer explicit contract enforcement—ensure changes reference userAccess, principal, and SecurityConfig.backend/smartjam-api/build.gradle (1)
6-8:⚠️ Potential issue | 🟠 MajorRestore the agreed JJWT BOM/
0.13.0setup.This re-pins JJWT to
0.12.3instead of the earlier0.13.0/BOM migration. Official JJWT docs still publish0.13.0as the install coordinates, and the project added a Maven BOM in0.12.7specifically to centralize these module versions. (github.com)Suggested Gradle update
- implementation('io.jsonwebtoken:jjwt-api:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') + implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0') + implementation 'io.jsonwebtoken:jjwt-api' + runtimeOnly 'io.jsonwebtoken:jjwt-impl' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson'#!/bin/bash set -euo pipefail echo "== JJWT coordinates in build files ==" rg -n -C2 'jjwt-(api|impl|jackson)|jjwt-bom' --glob 'build.gradle*' --glob 'pom.xml' echo echo "== JJWT API usages to recheck after the version switch ==" rg -n -C2 --glob '*.java' 'io\.jsonwebtoken|Jwts\.|parserBuilder\(|JwtParserBuilder|signWith\(|verifyWith\(' backend/smartjam-api/src/main/java🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/build.gradle` around lines 6 - 8, Replace the hard-pinned jjwt module coordinates with the official JJWT BOM at 0.13.0: add a platform/import of io.jsonwebtoken:jjwt-bom:0.13.0 in the dependencies block and change the current implementation('io.jsonwebtoken:jjwt-api:0.12.3') / runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') / runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') entries to reference the modules without explicit versions so they inherit from the BOM (keep the same artifactIds io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl, io.jsonwebtoken:jjwt-jackson); after updating, run the provided searches for io.jsonwebtoken, Jwts., parserBuilder(, JwtParserBuilder, signWith(, verifyWith( to validate API compatibility and adjust code if any 0.13.0 API changes surface.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd validation constraints to enable
@Validin controller.The record lacks validation annotations, so
@ValidinAuthControllerhas no effect. Add@NotBlank,@NotBlank,@Size(min=8, max=72)on password.Suggested fix
package com.smartjam.smartjamapi.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + -public record LoginRequest(String email, String password) {} +public record LoginRequest( + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8, max = 72) String password +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java` at line 3, The LoginRequest record lacks Bean Validation annotations so controller-level `@Valid` is ineffective; update the LoginRequest(String email, String password) record to annotate the email component with `@NotBlank` and `@Email` and annotate the password component with `@NotBlank` and `@Size`(min = 8, max = 72) so Spring validates inputs when AuthController receives a `@Valid` LoginRequest; ensure the javax.validation (or jakarta.validation) annotations are imported and applied to the record components.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java (1)
5-5: 🧹 Nitpick | 🔵 TrivialUse camelCase for record components with
@JsonPropertyfor JSON mapping.The
refresh_tokenandaccess_tokenfields use snake_case, which is non-idiomatic Java. Use camelCase with@JsonPropertyannotations.Suggested fix
package com.smartjam.smartjamapi.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import com.smartjam.smartjamapi.enums.AvailabilityStatus; -public record AuthResponse(String message, AvailabilityStatus status, String refresh_token, String access_token) {} +public record AuthResponse( + String message, + AvailabilityStatus status, + `@JsonProperty`("refresh_token") String refreshToken, + `@JsonProperty`("access_token") String accessToken +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java` at line 5, The record AuthResponse uses snake_case components; rename the components refresh_token and access_token to camelCase (refreshToken, accessToken) in the AuthResponse record declaration and add `@JsonProperty`("refresh_token") and `@JsonProperty`("access_token") annotations on those components so JSON serialization/deserialization keeps the original names; ensure you import com.fasterxml.jackson.annotation.JsonProperty and update any usages of AuthResponse component names accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd validation constraints to enable
@Validin controller.Same issue as
LoginRequest— missing validation annotations render@Validineffective. Add appropriate constraints.Suggested fix
package com.smartjam.smartjamapi.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + -public record RegisterRequest(String username, String email, String password) {} +public record RegisterRequest( + `@NotBlank` `@Size`(min = 3, max = 50) String username, + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8, max = 72) String password +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java` at line 3, RegisterRequest currently lacks Bean Validation annotations so `@Valid` in the controller has no effect; update the record declaration for RegisterRequest to annotate its components (username, email, password) with appropriate validation constraints (e.g., `@NotBlank` on username and password, `@Email` and `@NotBlank` on email, and `@Size` on password/username as needed) so Spring's validation runs when controllers use `@Valid` with RegisterRequest.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java (1)
10-14: 🛠️ Refactor suggestion | 🟠 MajorAdd
@NoArgsConstructorand@Builderfor JPA compliance and cleaner construction.JPA requires a no-arg constructor for entity instantiation.
@Builderwas also requested for use inAuthService.Suggested fix
import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Builder; import lombok.Setter; `@Entity` `@Table`(name = "refresh_tokens") `@Setter` `@Getter` +@NoArgsConstructor +@Builder public class RefreshTokenEntity {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java` around lines 10 - 14, The RefreshTokenEntity JPA class is missing a no-arg constructor and a builder; add Lombok's `@NoArgsConstructor` and `@Builder` annotations to the RefreshTokenEntity class declaration so JPA can instantiate the entity and AuthService can use the fluent builder; place the annotations alongside the existing `@Entity`, `@Table`, `@Setter`, and `@Getter` on the RefreshTokenEntity class and ensure Lombok is available in the project.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java (1)
3-8: 🧹 Nitpick | 🔵 TrivialClarify enum purpose or consider renaming.
The enum name
AvailabilityStatusis ambiguous in an authentication context. The values (AVAILABLE, UNAVAILABLE, PENDING, BANNED) suggest user account status rather than availability. Consider renaming toAccountStatusorUserStatusto better reflect its purpose, or add Javadoc explaining its intended use.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java` around lines 3 - 8, The enum AvailabilityStatus is ambiguous for representing account-related states; rename the enum to AccountStatus (or UserStatus) and update all usages to match the new identifier (e.g., replace AvailabilityStatus with AccountStatus) and keep the existing constants (AVAILABLE, UNAVAILABLE, PENDING, BANNED); alternatively, if renaming is undesired, add a clear Javadoc comment above the enum AvailabilityStatus explaining it models user/account lifecycle states so callers and future maintainers understand its intent.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java (1)
1-3:⚠️ Potential issue | 🟠 MajorRename class, add validation, and use camelCase.
Multiple issues already flagged:
- Class name
TokenDtois vague — considerRefreshTokenRequest- Missing
@NotBlankvalidation- Use camelCase with
@JsonPropertySuggested fix
package com.smartjam.smartjamapi.dto; -public record TokenDto(String refresh_token) {} +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; + +public record RefreshTokenRequest( + `@NotBlank` + `@JsonProperty`("refresh_token") + String refreshToken +) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java` around lines 1 - 3, Rename the vague TokenDto record to RefreshTokenRequest, change the record component name from refresh_token to camelCase refreshToken, add validation with `@NotBlank` on that component, and annotate it with `@JsonProperty`("refresh_token") so incoming snake_case JSON binds correctly; update imports to include javax.validation.constraints.NotBlank and com.fasterxml.jackson.annotation.JsonProperty and update any usages of TokenDto to the new RefreshTokenRequest name.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java (1)
20-33:⚠️ Potential issue | 🟠 MajorRemove the second
UserDetailsServicebean.Lines 22-30 create a repository-backed
UserDetailsServicethat returnsUserEntity, whileCustomUserDetailsServicealready returnsUserDetailsImpl. That splits authority/password mapping between two implementations and will break onceUserEntityis decoupled from Spring Security. InjectCustomUserDetailsServicehere and wire it intoDaoAuthenticationProviderdirectly.♻️ Suggested change
-import com.smartjam.smartjamapi.repository.UserRepository; +import com.smartjam.smartjamapi.config.CustomUserDetailsService; import lombok.RequiredArgsConstructor; @@ -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ - private final UserRepository userRepository; - - `@Bean` - public UserDetailsService userDetailsService() { - return email -> - userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Email not found")); - } + private final CustomUserDetailsService customUserDetailsService; `@Bean` public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService()); + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(customUserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java` around lines 20 - 33, Remove the repository-backed UserDetailsService bean and instead wire the existing CustomUserDetailsService into the DaoAuthenticationProvider: delete the userDetailsService() method that uses userRepository.findByEmail(...), ensure the authenticationProvider() uses the injected CustomUserDetailsService instance (not userDetailsService()) when creating DaoAuthenticationProvider, and keep the passwordEncoder() call as-is; update constructor/field injection to accept CustomUserDetailsService if necessary so authenticationProvider() references that concrete service.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java (1)
24-24: 🛠️ Refactor suggestion | 🟠 MajorKeep
UserEntityas a pure JPA model.
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javaalready exists as the Spring Security adapter. Keepingimplements UserDetailshere duplicates authority/password logic and is whyUserDetailsImpl.build(...)currently depends onuser.getPassword()instead of the persisted field. Remove the interface and these overrides, then let the adapter own the security projection.♻️ Suggested change
-import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; +// Spring Security types move to UserDetailsImpl `@Setter` `@Getter` `@NoArgsConstructor` `@AllArgsConstructor` `@Table`(name = "users") `@Entity` -public class UserEntity implements UserDetails { +public class UserEntity { @@ - `@Override` - public `@NonNull` Collection<? extends GrantedAuthority> getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); - } - - `@Override` - public `@NonNull` String getPassword() { - return passwordHash; - } }Also applies to: 55-63
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java` at line 24, Remove the Spring Security coupling from UserEntity: delete "implements UserDetails" on the UserEntity class and remove all UserDetails method overrides (e.g., getPassword(), getUsername(), getAuthorities(), isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled()) so UserEntity remains a pure JPA model; then ensure UserDetailsImpl.build(...) uses the persisted UserEntity fields directly (reference: UserDetailsImpl.build(...) and any calls to user.getPassword()) so the security projection lives solely in UserDetailsImpl.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
27-34:⚠️ Potential issue | 🟠 MajorPopulate authorities in
build()instead of returningList.of().Lines 27-33 leave every authenticated user authority-less, so
hasRole(...)and method-security checks will always fail. Carry the user's role into this type and return it here; while touching this, readpasswordHashdirectly so this class no longer depends onUserEntityimplementingUserDetails.🔐 Suggested change
import java.util.Collection; import java.util.List; import com.smartjam.smartjamapi.entity.UserEntity; import lombok.AllArgsConstructor; import lombok.Getter; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ `@Getter` private String email; private String password; + private Collection<? extends GrantedAuthority> authorities; public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPassword()); + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPasswordHash(), + List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))); } @@ public Collection<? extends GrantedAuthority> getAuthorities() { - return List.of(); + return authorities; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 27 - 34, UserDetailsImpl.build currently constructs the UserDetailsImpl without authorities and relies on UserEntity.getPassword; change build(UserEntity user) to extract the user's role(s) (e.g., user.getRole() or user.getRoles()) and convert them into a Collection<GrantedAuthority> (SimpleGrantedAuthority with "ROLE_*") to pass into the UserDetailsImpl constructor, and read the raw password hash field (passwordHash) from UserEntity instead of calling getPassword so UserDetailsImpl no longer depends on UserEntity implementing UserDetails; then have getAuthorities() simply return the stored authorities field.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java (1)
28-33:⚠️ Potential issue | 🟠 Major
/api/admin/**is unreachable with the current roles.The current role model only yields
ROLE_STUDENT/ROLE_TEACHER, sohasRole("ADMIN")will reject every request to this matcher. Either addADMINtoRoleor guard the route with an existing role.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java` around lines 28 - 33, The /api/admin/** matcher is currently guarded by hasRole("ADMIN") but your Role model only defines ROLE_STUDENT/ROLE_TEACHER, so every admin route is unreachable; fix by either adding an ADMIN entry to your Role enum and ensuring admin users are assigned Role.ADMIN (and any role-to-authority mapping uses the "ROLE_" prefix), or change the SecurityConfig authorizeHttpRequests rule (the requestMatchers("/api/admin/**") call using hasRole("ADMIN")) to use an existing role (e.g., hasRole("TEACHER") or hasAnyRole("TEACHER","STUDENT")) depending on intended access.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java (2)
31-34: 🧹 Nitpick | 🔵 TrivialAvoid rebuilding cryptographic helpers on every call.
getSigningKey()decodes/rebuilds the key every call, andgenerateRefreshToken()creates a newSecureRandomper call. Cache both once at class level.Also applies to: 36-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java` around lines 31 - 34, getSigningKey() currently decodes and rebuilds the Key on every call and generateRefreshToken() instantiates a new SecureRandom each time; change both to cached class-level fields (e.g., a final Key signingKey and a final SecureRandom secureRandom) initialized once (constructor or lazy init) and use those in getSigningKey() and generateRefreshToken() to avoid repeated decoding/instantiation and ensure thread-safety.
27-29:⚠️ Potential issue | 🟠 MajorAdd dedicated refresh-token expiration configuration.
The service exposes only
jwtExpiration, which pushes callers to reuse access-token TTL for refresh tokens. Add a separate refresh expiration property/getter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java` around lines 27 - 29, JwtService currently only exposes jwtExpiration (field jwtExpiration / its getter) so callers reuse access-token TTL for refresh tokens; add a new configuration field and getter for the refresh-token TTL (e.g., private long jwtRefreshExpiration with `@Value`("${security.jwt.refresh-expiration-time}") and a corresponding public getJwtRefreshExpiration() or `@Getter`) and update any callers to use getJwtRefreshExpiration() for refresh-token expiry; keep naming consistent with existing property convention (security.jwt.*) and ensure the new property is documented in application properties.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (4)
60-73:⚠️ Potential issue | 🟠 MajorRegistration still has a check-then-save race condition.
findByEmail(...).isPresent()+save(...)is non-atomic. Concurrent requests can both pass the check and one will fail at DB unique constraint. Save directly and translateDataIntegrityViolationException.Suggested fix
+import org.springframework.dao.DataIntegrityViolationException; @@ - public AuthResponse register(RegisterRequest request) { - boolean exists = repository.findByEmail(request.email()).isPresent(); - - if (exists) { - throw new IllegalStateException("The account exists, try login, please"); - } - - UserEntity userEntity = new UserEntity(); - userEntity.setUsername(request.username()); - userEntity.setEmail(request.email()); - userEntity.setPasswordHash(passwordEncoder.encode(request.password())); - userEntity.setRole(Role.STUDENT); - - repository.save(userEntity); + public AuthResponse register(RegisterRequest request) { + UserEntity userEntity = new UserEntity(); + userEntity.setUsername(request.username()); + userEntity.setEmail(request.email()); + userEntity.setPasswordHash(passwordEncoder.encode(request.password())); + userEntity.setRole(Role.STUDENT); + try { + repository.save(userEntity); + } catch (DataIntegrityViolationException ex) { + throw new IllegalStateException("The account exists, try login, please"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 60 - 73, Remove the non-atomic check-then-save pattern in AuthService (the findByEmail(...).isPresent() before repository.save) and instead attempt to persist the new UserEntity directly; wrap repository.save(userEntity) in a try-catch that catches org.springframework.dao.DataIntegrityViolationException (or the specific DB exception) and rethrow a user-friendly IllegalStateException("The account exists, try login, please") so concurrent registration races are handled atomically; keep username/email/password encoding and Role.STUDENT assignment as-is when constructing the UserEntity.
37-42:⚠️ Potential issue | 🟠 MajorUnify login failure responses to prevent account enumeration.
Line 39 and Line 41 return different failure reasons (
user missingvsinvalid password), which leaks account existence. Use one identical failure path/message for both.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 37 - 42, In AuthService (login flow using repository.findByEmail and passwordEncoder.matches) unify the failure path so both "user not found" and "invalid password" produce the same exception and message to avoid user enumeration; replace the current orElseThrow and subsequent IllegalStateException with a single check that attempts to load the user, then verifies the password, and on any failure throws the same exception type and identical message (e.g., "Invalid credentials"); ensure you reference the existing repository.findByEmail call and passwordEncoder.matches invocation so the change is limited to the login validation logic.
52-52:⚠️ Potential issue | 🟠 MajorRefresh-token expiry is incorrectly tied to access-token expiry.
All refresh records use
jwtService.getJwtExpiration(). This makes refresh tokens live as short as access tokens. Use a separate refresh TTL source.Also applies to: 82-82, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 52, The code in AuthService incorrectly sets refreshTokenEntity.setExpiresAt using jwtService.getJwtExpiration(), tying refresh-token TTL to access-token TTL; change these assignments to use the refresh-token TTL source (e.g., jwtService.getRefreshTokenExpiration() or the dedicated refreshTokenTtl config) wherever refreshTokenEntity.setExpiresAt(...) is used (the three occurrences in AuthService), and ensure the method that issues/creates refresh tokens (e.g., createRefreshToken / saveRefreshToken in AuthService) uses the separate refresh TTL so refresh tokens expire according to the refresh-specific configuration.
90-112:⚠️ Potential issue | 🟠 MajorRotate refresh token atomically and invalidate the old one.
getNewTokencreates a new refresh token but leaves the old one valid, enabling replay. Revoke/delete the used token in the same transaction after successful rotation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 90 - 112, getNewToken currently saves a new RefreshTokenEntity but leaves the used refreshToken valid, allowing replay; wrap the rotation in a single transactional operation (annotate getNewToken with `@Transactional`) and after creating/persisting the new RefreshTokenEntity call refreshTokenRepository.delete(refreshToken) (or set a revoked flag on the existing RefreshTokenEntity and save it) so the old token is invalidated in the same transaction; ensure you use the existing refreshToken variable (from findByToken) and refreshTokenRepository.save/delete so both persistence operations occur atomically.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
40-42:⚠️ Potential issue | 🟠 MajorHandle JWT parsing/validation exceptions inside the filter.
jwtService.extractUsername(jwt)/ validation can throw for malformed or invalid tokens. Without a guarded path, this can propagate as 500. Wrap JWT auth block intry/catch(JwtException,IllegalArgumentException), log, and continue chain without setting authentication.Also applies to: 43-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 40 - 42, Wrap the JWT extraction/validation in JwtAuthenticationFilter (around the call to jwtService.extractUsername(jwt) and subsequent token checks) with a try/catch for JwtException and IllegalArgumentException, log the exception (including the jwt or context) and then call filterChain.doFilter(request, response) without setting SecurityContext authentication so processing continues; do not rethrow—just skip authentication on error. Ensure the catch covers the block that includes token parsing, jwtService.extractUsername(jwt), and any jwtService.isTokenValid(...) checks.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (2)
53-57:⚠️ Potential issue | 🟠 MajorDo not expose raw exception messages in auth responses.
Line 56 sends
e.getMessage()back to clients. Return a sanitized message and keep full details only in logs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 53 - 57, The handler handleUnauthenticated in GlobalExceptionHandler currently returns e.getMessage() to clients; instead log the full exception details (e.g., log.warn("Unauthenticated", e) or log.warn("Unauthenticated: {}", e.getMessage(), e)) and construct ErrorResponseDto with a sanitized client-facing message like "Unauthorized" or "Authentication required" while keeping ErrorCode.UNAUTHORIZED and the timestamp (LocalDateTime.now()). Replace the use of e.getMessage() in the ErrorResponseDto constructor with the sanitized string but keep logging the original exception for diagnostics.
61-73:⚠️ Potential issue | 🟠 MajorSeparate auth-state failures from generic bad-request handling.
IllegalStateExceptionis currently mapped to 400. In this PR, auth flows throwIllegalStateExceptionfor credential/token failures, which should map to 401 (or dedicated auth exception mapping), not BAD_REQUEST.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 61 - 73, The handlerBadRequest method in GlobalExceptionHandler is currently catching IllegalStateException (treating it as BAD_REQUEST) which conflicts with auth flows; remove IllegalStateException from the exception list in handlerBadRequest and add a new ExceptionHandler method (e.g., handleUnauthorized or handlerUnauthorized) that catches IllegalStateException and returns a 401 (UNAUTHORIZED) ErrorResponseDto; update logging and ErrorResponseDto creation in the new handler similarly to handlerBadRequest but with ErrorCode reflecting authentication failure and HttpStatus.UNAUTHORIZED so auth/token credential failures are mapped correctly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
32-32:⚠️ Potential issue | 🟡 MinorFix HTTP status semantics and remove magic number statuses.
Line 32 should use
HttpStatus.CREATED(orResponseEntity.status(HttpStatus.CREATED)), and Line 38 should return200 OK(ResponseEntity.ok(...)) because/refreshdoes not create a persistent resource.Suggested fix
+import org.springframework.http.HttpStatus; @@ - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)); @@ - return ResponseEntity.status(201).body(authService.getNewToken(tokenDto)); + return ResponseEntity.ok(authService.getNewToken(tokenDto));Based on learnings: In SmartJam AuthController, token endpoints like
/refreshshould return200 OK, and numeric status literals should be replaced with typed constants.Also applies to: 38-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` at line 32, Replace magic numeric HTTP status literals in AuthController: change the register endpoint's return from ResponseEntity.status(201).body(authService.register(request)) to use ResponseEntity.status(HttpStatus.CREATED).body(... ) (or ResponseEntity.created(...) if location is available) and change the refresh endpoint to return 200 OK by using ResponseEntity.ok(authService.refresh(request)) instead of a numeric status. Update imports to include org.springframework.http.HttpStatus if needed and ensure both methods reference the same typed ResponseEntity usage rather than hard-coded integers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 22-25: The current loadUserByUsername method in
CustomUserDetailsService throws a UsernameNotFoundException that includes the
provided email (UserEntity lookup), which leaks PII into logs; change the thrown
exception message in loadUserByUsername (and the duplicate throw in the other
UserDetailsService in ApplicationConfig) to a generic message such as "User not
found" (no email or identifiers), leaving the email variable out of the
exception construction; ensure any related tests or callers still expect
UsernameNotFoundException but not a specific message.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java`:
- Line 18: Change the package-private field UploadService uploadService in the
UploadController to be private final to enforce encapsulation and immutability;
update the class constructor (or ensure there is a constructor) to accept an
UploadService parameter and assign it to this private final field (or use
`@Autowired` on the constructor) so the field remains immutable and properly
injected.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java`:
- Line 5: The UploadRequest record currently uses `@NotNull` on the fileName field
which allows empty or whitespace-only strings; replace `@NotNull` with
javax.validation.constraints.@NotBlank on the fileName parameter in the
UploadRequest record declaration so validation rejects null, empty, or
whitespace-only names (update the import if necessary and keep the symbol
UploadRequest and its fileName parameter unchanged).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java`:
- Line 5: The `@NotNull` on the response DTO UploadUrlResponse has no effect and
should be removed: delete the `@NotNull` annotation from the record declaration
(public record UploadUrlResponse(String uploadUrl) {}) and also remove the
now-unused import jakarta.validation.constraints.NotNull to simplify the class
and avoid misleading validation annotations on response objects.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Around line 19-20: Rename the RefreshTokenEntity.token field to jti and update
all related symbols (field, constructor params, getters/setters: e.g.,
getToken/setToken -> getJti/setJti) so the entity stores only the JTI UUID
string; keep the `@Column`(nullable = false, unique = true) annotation on jti,
ensure any code that creates or persists RefreshTokenEntity now supplies the JTI
value (UUID string) instead of the full JWT, and update database
migration/schema to rename the column accordingly so other classes referencing
RefreshTokenEntity (repositories, services) compile against the new jti API.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java`:
- Line 5: Rename the enum constant NON_FOUND_PAGE in ErrorCode to NOT_FOUND and
update all references/usages (e.g., switch statements, serialization keys,
tests, mappings) to use ErrorCode.NOT_FOUND; ensure any string values, JSON
(de)serialization or persistence mappings that relied on the old name are
updated or aliased to preserve compatibility (or add a temporary mapping if
needed) and run tests to verify there are no remaining references to
NON_FOUND_PAGE.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`:
- Around line 8-9: Replace storage and lookup of the full refresh token with the
JWT ID (jti) UUID: change RefreshTokenRepository by removing findByToken(...)
and adding findByJti(UUID jti) (or matching type) to query by jti; update
RefreshTokenEntity to remove the token field and add a jti UUID field (plus any
index/unique constraint); modify JwtService.generateRefreshToken() to emit a
signed JWT that includes a UUID jti claim (not opaque bytes); and update
AuthService to verify the refresh JWT, extract the jti claim from the verified
token and persist that jti to the repository (and use findByJti when
validating/revoking). Ensure all code paths that previously used the raw token
now use/compare the jti value.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 49-53: AuthService currently persists full refresh token strings
(RefreshTokenEntity.setToken) which increases replay risk; update
JwtService.generateRefreshToken() to emit a signed JWT containing a UUID jti
claim, modify RefreshTokenEntity to remove the token field and add a jti field,
change RefreshTokenRepository to expose findByJti(String jti) (replacing any
findByToken usage), and in AuthService verify the incoming refresh JWT signature
with JwtService before extracting the jti and persisting only the jti (and use
findByJti to load/validate existing refresh records).
- Around line 93-100: Replace the two NoSuchElementException throws in
AuthService with ResponseStatusException so auth failures map to proper HTTP
statuses: change the .orElseThrow for the refresh-token lookup (where
refreshToken is obtained) to throw new
ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token not found, try login,
please") and change the .orElseThrow for the user lookup (where
repository.findById(refreshToken.getUser().getId()) is called) to throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "User not found"); these changes
will ensure the refresh token and user-missing cases use ResponseStatusException
and are handled with 401/400 respectively.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Line 32: The region is inconsistent between init() (using Region.AP_EAST_1)
and generateUploadUrl() (using Region.US_EAST_1); replace both hardcoded Region
values with a single S3_REGION constant or configuration property and reference
that constant in both init() and generateUploadUrl() so both use the same Region
value (e.g., S3_REGION) to avoid signature/compatibility mismatches.
- Around line 49-56: The UploadService currently constructs a new S3Presigner
per request (see S3Presigner.builder() block); refactor by creating a Spring
`@Configuration` class that declares an S3Presigner `@Bean` configured with
endpointOverride, region, S3Configuration, and credentials (pull creds and
endpoint from properties), inject that S3Presigner into UploadService instead of
building it inline, remove the try-with-resources creation in UploadService and
use the injected presigner (do not close it per request; let Spring manage its
lifecycle), and update any unit tests or callers to use the bean.
- Around line 48-59: The generateUploadUrl method uses the incoming fileName
directly as the S3 key (PutObjectRequest.builder().key(fileName)), which opens
path-traversal/special-character risks; add input validation/sanitization: in
generateUploadUrl validate that fileName is non-empty, does not contain path
traversal patterns ("../" or leading "/"), and only contains allowed characters
(e.g. alphanumerics, dots, dashes, underscores) or explicitly reject/normalize
any others; better yet generate a safe object key (e.g. UUID) and append the
original file extension if needed before passing to PutObjectRequest so
downstream storage uses a sanitized, collision-resistant key.
- Line 64: Rename the mistyped local variable presignedUtl to presignedUrl in
UploadService.java where it is assigned from presignedGetObjectRequest.url();
update all references to presignedUtl in that method to presignedUrl so the name
is correct and consistent.
- Around line 34-35: In UploadService.java replace the hardcoded
StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin","minioadmin"))
(and the hardcoded endpoint) with values injected from
configuration/environment: add properties (or `@Value/`@ConfigurationProperties)
for the accessKey, secretKey and endpoint, load them via Spring and construct
StaticCredentialsProvider using those values at runtime; ensure you validate
presence and fail fast if missing. Use unique symbols AwsBasicCredentials.create
and StaticCredentialsProvider.create in the fix to locate the code to change.
---
Duplicate comments:
In `@backend/smartjam-api/build.gradle`:
- Around line 6-8: Replace the hard-pinned jjwt module coordinates with the
official JJWT BOM at 0.13.0: add a platform/import of
io.jsonwebtoken:jjwt-bom:0.13.0 in the dependencies block and change the current
implementation('io.jsonwebtoken:jjwt-api:0.12.3') /
runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') /
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') entries to reference the
modules without explicit versions so they inherit from the BOM (keep the same
artifactIds io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl,
io.jsonwebtoken:jjwt-jackson); after updating, run the provided searches for
io.jsonwebtoken, Jwts., parserBuilder(, JwtParserBuilder, signWith(, verifyWith(
to validate API compatibility and adjust code if any 0.13.0 API changes surface.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java`:
- Around line 20-33: Remove the repository-backed UserDetailsService bean and
instead wire the existing CustomUserDetailsService into the
DaoAuthenticationProvider: delete the userDetailsService() method that uses
userRepository.findByEmail(...), ensure the authenticationProvider() uses the
injected CustomUserDetailsService instance (not userDetailsService()) when
creating DaoAuthenticationProvider, and keep the passwordEncoder() call as-is;
update constructor/field injection to accept CustomUserDetailsService if
necessary so authenticationProvider() references that concrete service.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java`:
- Around line 28-33: The /api/admin/** matcher is currently guarded by
hasRole("ADMIN") but your Role model only defines ROLE_STUDENT/ROLE_TEACHER, so
every admin route is unreachable; fix by either adding an ADMIN entry to your
Role enum and ensuring admin users are assigned Role.ADMIN (and any
role-to-authority mapping uses the "ROLE_" prefix), or change the SecurityConfig
authorizeHttpRequests rule (the requestMatchers("/api/admin/**") call using
hasRole("ADMIN")) to use an existing role (e.g., hasRole("TEACHER") or
hasAnyRole("TEACHER","STUDENT")) depending on intended access.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Line 32: Replace magic numeric HTTP status literals in AuthController: change
the register endpoint's return from
ResponseEntity.status(201).body(authService.register(request)) to use
ResponseEntity.status(HttpStatus.CREATED).body(... ) (or
ResponseEntity.created(...) if location is available) and change the refresh
endpoint to return 200 OK by using
ResponseEntity.ok(authService.refresh(request)) instead of a numeric status.
Update imports to include org.springframework.http.HttpStatus if needed and
ensure both methods reference the same typed ResponseEntity usage rather than
hard-coded integers.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 5-6: Replace the manual SLF4J wiring in MainController (remove the
imports org.slf4j.Logger and org.slf4j.LoggerFactory and the private static
final Logger field) and annotate the class with Lombok's `@Slf4j` so logging
matches AuthController; ensure lombok.slf4j.Slf4j is imported or available and
update any logger usage to refer to the generated "log" instance inside
MainController.
- Line 30: Replace the truncated response string in MainController by updating
the return value that currently reads "You are auth" to a clear, complete phrase
such as "You are authenticated"; locate the return statement inside the
MainController class (the controller method returning the auth message) and
modify the literal to the full message so API responses are readable and
unambiguous.
- Around line 19-25: In MainController.userAccess, remove the
System.out.println(principal) diagnostic and replace the log.error("Call
userAccess") with an appropriate level (e.g., log.info or log.debug) to reflect
a successful call; then verify the security contract in SecurityConfig (look for
requestMatchers/anyRequest/authenticated for /secured/**) and if those endpoints
are guaranteed authenticated, remove the null-principal branch (the if
(principal == null) return "Anonymous";) or update it to throw/return an
explicit error if you prefer explicit contract enforcement—ensure changes
reference userAccess, principal, and SecurityConfig.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java`:
- Line 5: The record AuthResponse uses snake_case components; rename the
components refresh_token and access_token to camelCase (refreshToken,
accessToken) in the AuthResponse record declaration and add
`@JsonProperty`("refresh_token") and `@JsonProperty`("access_token") annotations on
those components so JSON serialization/deserialization keeps the original names;
ensure you import com.fasterxml.jackson.annotation.JsonProperty and update any
usages of AuthResponse component names accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java`:
- Line 3: The LoginRequest record lacks Bean Validation annotations so
controller-level `@Valid` is ineffective; update the LoginRequest(String email,
String password) record to annotate the email component with `@NotBlank` and
`@Email` and annotate the password component with `@NotBlank` and `@Size`(min = 8, max
= 72) so Spring validates inputs when AuthController receives a `@Valid`
LoginRequest; ensure the javax.validation (or jakarta.validation) annotations
are imported and applied to the record components.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`:
- Line 3: RegisterRequest currently lacks Bean Validation annotations so `@Valid`
in the controller has no effect; update the record declaration for
RegisterRequest to annotate its components (username, email, password) with
appropriate validation constraints (e.g., `@NotBlank` on username and password,
`@Email` and `@NotBlank` on email, and `@Size` on password/username as needed) so
Spring's validation runs when controllers use `@Valid` with RegisterRequest.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java`:
- Around line 1-3: Rename the vague TokenDto record to RefreshTokenRequest,
change the record component name from refresh_token to camelCase refreshToken,
add validation with `@NotBlank` on that component, and annotate it with
`@JsonProperty`("refresh_token") so incoming snake_case JSON binds correctly;
update imports to include javax.validation.constraints.NotBlank and
com.fasterxml.jackson.annotation.JsonProperty and update any usages of TokenDto
to the new RefreshTokenRequest name.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Around line 10-14: The RefreshTokenEntity JPA class is missing a no-arg
constructor and a builder; add Lombok's `@NoArgsConstructor` and `@Builder`
annotations to the RefreshTokenEntity class declaration so JPA can instantiate
the entity and AuthService can use the fluent builder; place the annotations
alongside the existing `@Entity`, `@Table`, `@Setter`, and `@Getter` on the
RefreshTokenEntity class and ensure Lombok is available in the project.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Line 24: Remove the Spring Security coupling from UserEntity: delete
"implements UserDetails" on the UserEntity class and remove all UserDetails
method overrides (e.g., getPassword(), getUsername(), getAuthorities(),
isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(),
isEnabled()) so UserEntity remains a pure JPA model; then ensure
UserDetailsImpl.build(...) uses the persisted UserEntity fields directly
(reference: UserDetailsImpl.build(...) and any calls to user.getPassword()) so
the security projection lives solely in UserDetailsImpl.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java`:
- Around line 3-8: The enum AvailabilityStatus is ambiguous for representing
account-related states; rename the enum to AccountStatus (or UserStatus) and
update all usages to match the new identifier (e.g., replace AvailabilityStatus
with AccountStatus) and keep the existing constants (AVAILABLE, UNAVAILABLE,
PENDING, BANNED); alternatively, if renaming is undesired, add a clear Javadoc
comment above the enum AvailabilityStatus explaining it models user/account
lifecycle states so callers and future maintainers understand its intent.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 53-57: The handler handleUnauthenticated in GlobalExceptionHandler
currently returns e.getMessage() to clients; instead log the full exception
details (e.g., log.warn("Unauthenticated", e) or log.warn("Unauthenticated: {}",
e.getMessage(), e)) and construct ErrorResponseDto with a sanitized
client-facing message like "Unauthorized" or "Authentication required" while
keeping ErrorCode.UNAUTHORIZED and the timestamp (LocalDateTime.now()). Replace
the use of e.getMessage() in the ErrorResponseDto constructor with the sanitized
string but keep logging the original exception for diagnostics.
- Around line 61-73: The handlerBadRequest method in GlobalExceptionHandler is
currently catching IllegalStateException (treating it as BAD_REQUEST) which
conflicts with auth flows; remove IllegalStateException from the exception list
in handlerBadRequest and add a new ExceptionHandler method (e.g.,
handleUnauthorized or handlerUnauthorized) that catches IllegalStateException
and returns a 401 (UNAUTHORIZED) ErrorResponseDto; update logging and
ErrorResponseDto creation in the new handler similarly to handlerBadRequest but
with ErrorCode reflecting authentication failure and HttpStatus.UNAUTHORIZED so
auth/token credential failures are mapped correctly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 40-42: Wrap the JWT extraction/validation in
JwtAuthenticationFilter (around the call to jwtService.extractUsername(jwt) and
subsequent token checks) with a try/catch for JwtException and
IllegalArgumentException, log the exception (including the jwt or context) and
then call filterChain.doFilter(request, response) without setting
SecurityContext authentication so processing continues; do not rethrow—just skip
authentication on error. Ensure the catch covers the block that includes token
parsing, jwtService.extractUsername(jwt), and any jwtService.isTokenValid(...)
checks.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java`:
- Around line 31-34: getSigningKey() currently decodes and rebuilds the Key on
every call and generateRefreshToken() instantiates a new SecureRandom each time;
change both to cached class-level fields (e.g., a final Key signingKey and a
final SecureRandom secureRandom) initialized once (constructor or lazy init) and
use those in getSigningKey() and generateRefreshToken() to avoid repeated
decoding/instantiation and ensure thread-safety.
- Around line 27-29: JwtService currently only exposes jwtExpiration (field
jwtExpiration / its getter) so callers reuse access-token TTL for refresh
tokens; add a new configuration field and getter for the refresh-token TTL
(e.g., private long jwtRefreshExpiration with
`@Value`("${security.jwt.refresh-expiration-time}") and a corresponding public
getJwtRefreshExpiration() or `@Getter`) and update any callers to use
getJwtRefreshExpiration() for refresh-token expiry; keep naming consistent with
existing property convention (security.jwt.*) and ensure the new property is
documented in application properties.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 27-34: UserDetailsImpl.build currently constructs the
UserDetailsImpl without authorities and relies on UserEntity.getPassword; change
build(UserEntity user) to extract the user's role(s) (e.g., user.getRole() or
user.getRoles()) and convert them into a Collection<GrantedAuthority>
(SimpleGrantedAuthority with "ROLE_*") to pass into the UserDetailsImpl
constructor, and read the raw password hash field (passwordHash) from UserEntity
instead of calling getPassword so UserDetailsImpl no longer depends on
UserEntity implementing UserDetails; then have getAuthorities() simply return
the stored authorities field.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 60-73: Remove the non-atomic check-then-save pattern in
AuthService (the findByEmail(...).isPresent() before repository.save) and
instead attempt to persist the new UserEntity directly; wrap
repository.save(userEntity) in a try-catch that catches
org.springframework.dao.DataIntegrityViolationException (or the specific DB
exception) and rethrow a user-friendly IllegalStateException("The account
exists, try login, please") so concurrent registration races are handled
atomically; keep username/email/password encoding and Role.STUDENT assignment
as-is when constructing the UserEntity.
- Around line 37-42: In AuthService (login flow using repository.findByEmail and
passwordEncoder.matches) unify the failure path so both "user not found" and
"invalid password" produce the same exception and message to avoid user
enumeration; replace the current orElseThrow and subsequent
IllegalStateException with a single check that attempts to load the user, then
verifies the password, and on any failure throws the same exception type and
identical message (e.g., "Invalid credentials"); ensure you reference the
existing repository.findByEmail call and passwordEncoder.matches invocation so
the change is limited to the login validation logic.
- Line 52: The code in AuthService incorrectly sets
refreshTokenEntity.setExpiresAt using jwtService.getJwtExpiration(), tying
refresh-token TTL to access-token TTL; change these assignments to use the
refresh-token TTL source (e.g., jwtService.getRefreshTokenExpiration() or the
dedicated refreshTokenTtl config) wherever refreshTokenEntity.setExpiresAt(...)
is used (the three occurrences in AuthService), and ensure the method that
issues/creates refresh tokens (e.g., createRefreshToken / saveRefreshToken in
AuthService) uses the separate refresh TTL so refresh tokens expire according to
the refresh-specific configuration.
- Around line 90-112: getNewToken currently saves a new RefreshTokenEntity but
leaves the used refreshToken valid, allowing replay; wrap the rotation in a
single transactional operation (annotate getNewToken with `@Transactional`) and
after creating/persisting the new RefreshTokenEntity call
refreshTokenRepository.delete(refreshToken) (or set a revoked flag on the
existing RefreshTokenEntity and save it) so the old token is invalidated in the
same transaction; ensure you use the existing refreshToken variable (from
findByToken) and refreshTokenRepository.save/delete so both persistence
operations occur atomically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 56e1053d-bdfb-4198-87b7-29a8428e8274
📒 Files selected for processing (28)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.javabackend/smartjam-api/src/main/resources/application.yaml
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
Outdated
Show resolved
Hide resolved
| public UploadUrlResponse generateUploadUrl(String fileName) { | ||
| try (S3Presigner presigner = S3Presigner.builder() | ||
| .endpointOverride(URI.create(S3_ENDPOINT)) | ||
| .region(Region.US_EAST_1) | ||
| .serviceConfiguration( | ||
| S3Configuration.builder().pathStyleAccessEnabled(true).build()) | ||
| .credentialsProvider( | ||
| StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) | ||
| .build()) { | ||
|
|
||
| PutObjectRequest putObjectRequest = | ||
| PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName).build(); |
There was a problem hiding this comment.
Missing input validation on fileName — potential path traversal.
The fileName parameter is used directly as the S3 object key without validation. A malicious input like ../../../etc/passwd or filenames with special characters could cause unexpected behavior or security issues depending on downstream usage.
🛡️ Suggested validation
public UploadUrlResponse generateUploadUrl(String fileName) {
+ if (fileName == null || fileName.isBlank()) {
+ throw new IllegalArgumentException("fileName must not be blank");
+ }
+ if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
+ throw new IllegalArgumentException("Invalid fileName");
+ }
try (S3Presigner presigner = S3Presigner.builder()Consider also using a UUID as the object key (as noted in learnings for future plans) to eliminate filename concerns entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`
around lines 48 - 59, The generateUploadUrl method uses the incoming fileName
directly as the S3 key (PutObjectRequest.builder().key(fileName)), which opens
path-traversal/special-character risks; add input validation/sanitization: in
generateUploadUrl validate that fileName is non-empty, does not contain path
traversal patterns ("../" or leading "/"), and only contains allowed characters
(e.g. alphanumerics, dots, dashes, underscores) or explicitly reject/normalize
any others; better yet generate a safe object key (e.g. UUID) and append the
original file extension if needed before passing to PutObjectRequest so
downstream storage uses a sanitized, collision-resistant key.
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (16)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (2)
64-65:⚠️ Potential issue | 🟡 MinorTypo:
presignedUtl→presignedUrl.✏️ Fix
- URL presignedUtl = presignedGetObjectRequest.url(); - return new UploadUrlResponse(presignedUtl.toString()); + URL presignedUrl = presignedGetObjectRequest.url(); + return new UploadUrlResponse(presignedUrl.toString());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 64 - 65, In UploadService (method returning UploadUrlResponse) fix the local variable name typo: rename presignedUtl to presignedUrl wherever declared and used (e.g., the line assigning presignedGetObjectRequest.url() and the return that uses it) so the identifier matches correct spelling and any other references in the method are updated accordingly.
58-59:⚠️ Potential issue | 🟠 MajorMissing input validation on
fileName— potential path traversal risk.The
fileNameis used directly as the S3 object key without sanitization. Malicious inputs like../../../etc/passwdor names with special characters could cause security issues.🛡️ Suggested validation
public UploadUrlResponse generateUploadUrl(String fileName) { + if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) { + throw new IllegalArgumentException("Invalid fileName"); + } try (S3Presigner presigner = S3Presigner.builder()Per learnings, S3 object keys are planned to be UUIDs in future PRs, which would eliminate this concern entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 58 - 59, The code uses the incoming fileName directly when building the S3 PutObjectRequest (PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName)), which allows path-traversal or malicious keys; validate and sanitize fileName in UploadService before creating the PutObjectRequest: reject or normalize segments like “..”, remove or encode path separators and control characters, enforce an allowed character/length regex or replace with a generated safe identifier (e.g., UUID) and only then pass the sanitized value to PutObjectRequest.key(...); ensure any validation errors are handled/returned appropriately.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)
9-15:⚠️ Potential issue | 🟠 MajorUse
UUIDas the repository ID type.
UserEntity.idisUUID, butJpaRepository<UserEntity, Long>makes inherited CRUD methods type-incorrect and forces the extrafindById(UUID)overload. Parameterize the repository withUUIDand drop the customfindById.🧩 Proposed fix
-public interface UserRepository extends JpaRepository<UserEntity, Long> { +public interface UserRepository extends JpaRepository<UserEntity, UUID> { Optional<UserEntity> findByEmail(String login); - - Optional<UserEntity> findById(UUID id); Optional<UserEntity> findUserEntitiesByUsername(String username);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` around lines 9 - 15, The repository is parameterized with the wrong ID type: change the interface declaration from JpaRepository<UserEntity, Long> to JpaRepository<UserEntity, UUID> so inherited CRUD methods use UUID correctly (update the generic type on UserRepository), and remove the redundant custom findById(UUID id) method; ensure imports/reference to UUID remain and keep other methods like findByEmail and findUserEntitiesByUsername untouched.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (1)
50-55:⚠️ Potential issue | 🟠 MajorDon't treat every
IllegalArgumentExceptionas401.
IllegalArgumentExceptionis too generic for authentication failures. This mapping will turn unrelated bad input intoUNAUTHORIZED, whileIllegalStateExceptionstill falls through the bad-request handler. Route only the auth-specific exception type to401—or swap this handler toIllegalStateExceptionif that's whatAuthServicethrows.🔁 Minimal fix if auth currently throws `IllegalStateException`
- `@ExceptionHandler`(IllegalArgumentException.class) - public ResponseEntity<ErrorResponseDto> handleUnauthenticated(IllegalArgumentException e) { + `@ExceptionHandler`(IllegalStateException.class) + public ResponseEntity<ErrorResponseDto> handleUnauthenticated(IllegalStateException e) { log.warn("Unauthenticated: {}", e.getMessage()); return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); } - `@ExceptionHandler`(exception = {IllegalStateException.class, MethodArgumentNotValidException.class}) + `@ExceptionHandler`(exception = {IllegalArgumentException.class, MethodArgumentNotValidException.class}) public ResponseEntity<ErrorResponseDto> handlerBadRequest(Exception e) {Also applies to: 57-61
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 50 - 55, The handler in GlobalExceptionHandler (method handleUnauthenticated) incorrectly maps all IllegalArgumentException to 401; change the exception type handled to the specific auth exception your AuthService throws (e.g., AuthenticationException or IllegalStateException if AuthService uses that) so only auth failures return UNAUTHORIZED, and leave IllegalArgumentException to the bad-request handler; update the `@ExceptionHandler` annotation and method signature for handleUnauthenticated accordingly and ensure ErrorCode.UNAUTHORIZED and the log remain.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java (1)
20-21:⚠️ Potential issue | 🟠 MajorPersist the refresh JWT
jti, not the full token.This still stores a replayable credential in the database. The confirmed SmartJam flow is JTI-based refresh-token revocation, so this field should be
jti, withJwtService/AuthServiceextracting and persisting the verified JWT ID instead of the raw token string.🔐 Proposed direction
- `@Column`(nullable = false, unique = true) - private String token; + `@Column`(nullable = false, unique = true) + private String jti;Based on learnings: "In the SmartJam project, the JTI-based refresh token approach has been confirmed: store only the
jti(UUID) from the refresh JWT in RefreshTokenEntity.jti (not the full token string)."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java` around lines 20 - 21, RefreshTokenEntity currently persists the full refresh token string in the field named "token", which stores a replayable credential; change the entity to store only the JWT ID (jti) by replacing/renaming the field to "jti" (UUID/String as appropriate) and update all usages: ensure JwtService (or the component that verifies tokens) extracts the verified JWT ID and AuthService saves that jti into RefreshTokenEntity instead of the raw token, and adjust lookup/revocation logic to query by jti rather than token.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (2)
32-32: 🧹 Nitpick | 🔵 TrivialUse
HttpStatus.CREATEDinstead of magic number201.Replace the numeric literal with the typed constant for readability and maintainability.
- return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));Add the import:
import org.springframework.http.HttpStatus;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` at line 32, Replace the numeric HTTP status literal in AuthController's registration response with the typed constant: change ResponseEntity.status(201) to ResponseEntity.status(HttpStatus.CREATED) in the method that returns authService.register(request) and add the import for org.springframework.http.HttpStatus at the top of the file.
35-39:⚠️ Potential issue | 🟡 MinorRefresh endpoint should return
200 OK, not201 Created.The
/refreshendpoint generates new tokens for an existing session—it's not creating a permanent resource. Based on learnings, token endpoints should return200 OK.Proposed fix
`@PostMapping`("/refresh") public ResponseEntity<AuthResponse> getNewToken(`@RequestBody` `@Valid` RefreshTokenRequest refreshTokenRequest) { log.info("Calling getNewToken"); - return ResponseEntity.status(201).body(authService.getNewToken(refreshTokenRequest)); + return ResponseEntity.ok(authService.getNewToken(refreshTokenRequest)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 35 - 39, The endpoint in AuthController (method getNewToken handling RefreshTokenRequest) incorrectly returns 201 Created via ResponseEntity.status(201).body(authService.getNewToken(...)); change it to return 200 OK instead—use ResponseEntity.ok(...) (or ResponseEntity.status(200)) so the refresh token response from authService.getNewToken(refreshTokenRequest) is returned with HTTP 200.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (1)
23-26:⚠️ Potential issue | 🟡 MinorTypo: "auth" should be "authenticated".
The response message appears truncated.
`@GetMapping`("/hello") public String hello() { - return "You are auth"; + return "You are authenticated"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 23 - 26, The response string in MainController.hello() is truncated: update the method (public String hello()) to return the full word "You are authenticated" instead of "You are auth"; locate the hello() handler in the MainController class and replace the returned literal with the corrected message.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java (1)
16-19:⚠️ Potential issue | 🟠 MajorDuplicate
UserDetailsServicebean registration.
CustomUserDetailsServiceis already annotated with@Serviceand implementsUserDetailsService, making it a bean. This additional@Beanmethod creates a secondUserDetailsServicebean, which can causeNoUniqueBeanDefinitionExceptionor unpredictable injection behavior. Remove this bean and injectCustomUserDetailsServicedirectly where needed.Proposed fix: Remove redundant bean
`@Configuration` `@RequiredArgsConstructor` public class ApplicationConfig { - private final CustomUserDetailsService customUserDetailsService; - - `@Bean` - public UserDetailsService userDetailsService() { - return customUserDetailsService::loadUserByUsername; - } - `@Bean` public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java` around lines 16 - 19, Remove the redundant UserDetailsService bean method in ApplicationConfig: the method userDetailsService() annotated with `@Bean` returns customUserDetailsService::loadUserByUsername but CustomUserDetailsService is already a `@Service` implementing UserDetailsService, causing duplicate bean registration; delete the userDetailsService() bean from ApplicationConfig and update consumers to inject CustomUserDetailsService (or UserDetailsService) directly via constructor injection where needed.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (5)
53-53:⚠️ Potential issue | 🟠 MajorRefresh token expiration uses access token expiration time.
jwtService.getJwtExpiration()is used for both access and refresh tokens. Refresh tokens typically have much longer lifespans (days/weeks) than access tokens (minutes/hours). Consider adding a separaterefreshTokenExpirationconfiguration.#!/bin/bash # Check if there's a separate refresh token expiration configuration rg -n "refresh.*expir|expir.*refresh" --type=yaml --type=properties -i rg -n "refreshTokenExpiration|refresh-token-expiration" --type=java -iAlso applies to: 82-82, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 53, The refresh token expiry is incorrectly using jwtService.getJwtExpiration(); update AuthService where refreshTokenEntity.setExpiresAt(...) is called to use a dedicated refresh expiration value instead—introduce a configuration property (e.g., refreshTokenExpiration) and expose it via JwtService (add getRefreshTokenExpiration or similar), then call jwtService.getRefreshTokenExpiration() (or read the config) and apply Instant.now().plusMillis/plus(...) accordingly; also update JwtService and your application properties binding to include the new refreshTokenExpiration setting so access tokens still use getJwtExpiration() and refresh tokens use the new value.
62-64:⚠️ Potential issue | 🟠 MajorRace condition:
existsByEmailcheck-then-save is not atomic.Concurrent registration requests for the same email can both pass the existence check and attempt to save, causing one to fail with an unhandled
DataIntegrityViolationException. Remove the pre-check and catch the constraint violation instead.Proposed fix
+import org.springframework.dao.DataIntegrityViolationException; public AuthResponse register(RegisterRequest request) { - - if (repository.existsByEmail(request.email())) { - throw new IllegalStateException("The account exists, try login, please"); - } - - UserEntity userEntity = new UserEntity(); - userEntity.setUsername(request.username()); - userEntity.setEmail(request.email()); - userEntity.setPasswordHash(passwordEncoder.encode(request.password())); - userEntity.setRole(Role.STUDENT); - - repository.save(userEntity); + UserEntity userEntity = new UserEntity(); + userEntity.setUsername(request.username()); + userEntity.setEmail(request.email()); + userEntity.setPasswordHash(passwordEncoder.encode(request.password())); + userEntity.setRole(Role.STUDENT); + + try { + repository.save(userEntity); + } catch (DataIntegrityViolationException ex) { + throw new IllegalStateException("The account exists, try login, please"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 62 - 64, Remove the non-atomic existsByEmail pre-check in AuthService (the block calling repository.existsByEmail(request.email())) and instead perform the save inside a try-catch: call repository.save(...) (or the current persist method you use) and catch org.springframework.dao.DataIntegrityViolationException to detect duplicate-email constraint violations, then throw the same IllegalStateException("The account exists, try login, please") (or wrap with that message) so concurrent requests are handled safely; ensure you import DataIntegrityViolationException and only catch/handle the constraint case without swallowing other persistence exceptions.
89-115:⚠️ Potential issue | 🟠 MajorOld refresh token not invalidated after use—allows token replay.
When refreshing tokens, the old
RefreshTokenEntityremains valid in the database. This allows reuse of old refresh tokens, which is a security concern. Delete or mark the old token as used after generating the new one.Proposed fix
String accessToken = jwtService.generateAccessToken(userDetails); String newRefreshToken = jwtService.generateRefreshToken(); + // Invalidate old refresh token + refreshTokenRepository.delete(refreshToken); RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); refreshTokenEntity.setToken(newRefreshToken); refreshTokenEntity.setUser(userEntity); refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); refreshTokenRepository.save(refreshTokenEntity);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 89 - 115, In getNewToken, the existing RefreshTokenEntity retrieved via refreshTokenRepository.findByToken(...) is left valid, enabling token replay; after generating the new refresh token you must invalidate the old one—either delete it via refreshTokenRepository.delete(refreshToken) or set a "revoked/used" flag on the existing RefreshTokenEntity and save it (e.g., refreshToken.setRevoked(true); refreshTokenRepository.save(refreshToken)) before persisting the new RefreshTokenEntity and returning the AuthResponse; update the RefreshTokenEntity model and repository if you choose the flag approach.
37-43:⚠️ Potential issue | 🟠 MajorUse a single generic auth failure response to prevent account enumeration.
Lines 40 and 42 return different error messages ("Login not found" vs "Invalid password"), allowing attackers to enumerate valid accounts. Return identical messages for both cases.
Proposed fix
`@Transactional` public AuthResponse login(LoginRequest request) { UserEntity userEntity = repository .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + .orElseThrow(() -> new IllegalStateException("Invalid credentials")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { - throw new IllegalStateException("Invalid password"); + throw new IllegalStateException("Invalid credentials"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 37 - 43, AuthService.login currently throws different exceptions for missing users (repository.findByEmail -> NoSuchElementException with "Login not found...") and wrong passwords (IllegalStateException "Invalid password"), which enables account enumeration; change the method to always throw the same generic authentication failure (e.g., new AuthenticationException or IllegalArgumentException with a single message like "Invalid email or password") for both the case where repository.findByEmail returns empty and when passwordEncoder.matches fails—ensure both branches use that same exception type and message so AuthService.login, repository.findByEmail handling, and passwordEncoder.matches comparisons do not reveal which check failed.
50-55:⚠️ Potential issue | 🟠 MajorImplement JTI-based refresh tokens per project guidelines.
Current implementation stores full opaque token strings in the database. Per project architecture, refresh tokens should be signed JWTs with a UUID
jticlaim, storing only thejtiin RefreshTokenEntity instead. This eliminates replay risk if the database is compromised. Currently,JwtService.generateRefreshToken()generates random Base64 strings with no JWT signature, andRefreshTokenEntitystores the full token.Refactor to:
- Update
RefreshTokenEntity: replacetokenfield withjtifield (UUID)- Update
JwtService.generateRefreshToken(): generate signed JWT with UUIDjticlaim instead of opaque random string- Update
RefreshTokenRepository: changefindByToken(String)tofindByJti(String)for lookups- Update
AuthServiceto extractjtifrom JWT after verification before storingApplies to lines 50–55, 79–84, 106–111.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 50 - 55, RefreshToken handling must switch from storing opaque strings to a JTI-based signed JWT: update the RefreshTokenEntity by replacing the token field with a jti (UUID) field and use that field everywhere; change JwtService.generateRefreshToken() to create a signed JWT containing a UUID jti claim (and return the JWT string for client use); update RefreshTokenRepository by renaming findByToken(String) to findByJti(String) and adjust its queries; and modify AuthService where you currently create and save the RefreshTokenEntity (and where you lookup by token) to first verify the incoming JWT via JwtService, extract the jti claim (UUID) and store only that jti in RefreshTokenEntity and use findByJti(...) for lookups.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
42-57:⚠️ Potential issue | 🟠 MajorAdd exception handling for malformed or invalid tokens.
If
jwtService.extractUsername(jwt)throws an exception (e.g., malformed token, invalid signature, expired token), the filter propagates it as a 500 error. Wrap JWT processing in a try-catch and let Spring Security handle the unauthorized response.Proposed fix
final String jwt = authHeader.substring(7); - final String email = jwtService.extractUsername(jwt); - - log.info("Filter for {}", email); - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); - - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authToken); + try { + final String email = jwtService.extractUsername(jwt); + + log.debug("Processing JWT for: {}", email); + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authToken); + } } + } catch (Exception ex) { + log.warn("JWT authentication failed: {}", ex.getMessage()); + // Don't set authentication - Spring Security will handle 401 } filterChain.doFilter(request, response);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 42 - 57, Wrap the JWT processing in JwtAuthenticationFilter in a try-catch so exceptions from jwtService.extractUsername(...) or jwtService.isTokenValid(...) are caught and do not propagate as 500s; specifically, surround the block that calls jwtService.extractUsername(jwt), customUserDetailsService.loadUserByUsername(email) and jwtService.isTokenValid(jwt, userDetails) with a catch (Exception e) that logs the failure (use log.debug or log.warn with e) and simply returns from the filter method (or proceeds without setting SecurityContextHolder.getContext().setAuthentication(...)) so Spring Security will treat the request as unauthenticated and produce the correct 401/403 response.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
32-35:⚠️ Potential issue | 🟠 MajorEmpty authorities break role-based authorization.
getAuthorities()returns an empty list, so any role-based security checks (e.g.,@PreAuthorize("hasRole('...')"),.hasRole()inSecurityConfig) will always fail. Map the user's role to aGrantedAuthority.Proposed fix
+import org.springframework.security.core.authority.SimpleGrantedAuthority; +import java.util.Collections; `@AllArgsConstructor` `@NullMarked` public class UserDetailsImpl implements UserDetails { `@Getter` private UUID id; private String username; `@Getter` private String email; private String password; + private Collection<? extends GrantedAuthority> authorities; - public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash()); + public static UserDetailsImpl build(UserEntity user) { + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPasswordHash(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))); } `@Override` public Collection<? extends GrantedAuthority> getAuthorities() { - return List.of(); + return authorities; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 32 - 35, The getAuthorities() in UserDetailsImpl currently returns an empty list which breaks role checks; update getAuthorities() in class UserDetailsImpl to map the user's role field into a GrantedAuthority (e.g., create a SimpleGrantedAuthority with the role prefixed with "ROLE_" or as your SecurityConfig expects) and return a list/collection containing that authority; ensure you reference the user's role getter/field in UserDetailsImpl and add the necessary import for org.springframework.security.core.authority.SimpleGrantedAuthority.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 15-21: In MainController's userAccess(Principal principal) method,
remove the redundant log so the principal's name is logged only once; replace
the two calls to log.info(principal.getName()) and log.info("Call userAccess")
with a single, meaningful log statement (inside userAccess) that includes both
context and the principal name (e.g., "userAccess called for user: {name}") to
avoid duplicate messages.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java`:
- Line 8: AuthResponse currently exposes AvailabilityStatus which is misleading
for auth flows; introduce a dedicated enum (e.g., AuthenticationStatus or
AuthResult) and replace the AvailabilityStatus reference in AuthResponse with
that new enum, update the AuthResponse constructor/getters/setters and any
serialization annotations, and refactor any callers (token
creation/registration/login/refresh handlers) that set or read
AvailabilityStatus to use the new AuthenticationStatus values so the public API
clearly reflects authentication outcomes.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java`:
- Around line 3-6: The DTO RefreshTokenRequest lacks a validation constraint on
its refreshToken component so `@Valid` on the controller won't reject null/blank
values; add a validation annotation (e.g. `@NotBlank` from javax.validation or
jakarta.validation.constraints) to the refreshToken record component and import
the corresponding constraint so blank or null tokens produce a 400 at the DTO
boundary; ensure the controller parameter remains annotated with `@Valid` to
trigger validation.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Around line 25-26: The username field in UserEntity lacks a DB-level
uniqueness constraint while repository methods (findUserEntitiesByUsername(),
existsByUsername()) and registration assume uniqueness; add a uniqueness
constraint to the schema (e.g., annotate the username field in UserEntity with a
unique constraint or add a `@Table`(uniqueConstraints=...)) and update the
registration flow to check existsByUsername() before save and also handle race
conditions by catching the persistence exception (e.g.,
DataIntegrityViolationException/ConstraintViolationException) when saving a
duplicate username, returning an appropriate error response.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Line 45: In JwtAuthenticationFilter change the per-request logging call that
currently uses log.info("Filter for {}", email) to use log.debug instead (i.e.,
log.debug("Filter for {}", email)) so routine request processing is logged at
DEBUG level; locate this call inside the JwtAuthenticationFilter class (the
filter method where email is logged) and update the log level accordingly.
---
Duplicate comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java`:
- Around line 16-19: Remove the redundant UserDetailsService bean method in
ApplicationConfig: the method userDetailsService() annotated with `@Bean` returns
customUserDetailsService::loadUserByUsername but CustomUserDetailsService is
already a `@Service` implementing UserDetailsService, causing duplicate bean
registration; delete the userDetailsService() bean from ApplicationConfig and
update consumers to inject CustomUserDetailsService (or UserDetailsService)
directly via constructor injection where needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Line 32: Replace the numeric HTTP status literal in AuthController's
registration response with the typed constant: change ResponseEntity.status(201)
to ResponseEntity.status(HttpStatus.CREATED) in the method that returns
authService.register(request) and add the import for
org.springframework.http.HttpStatus at the top of the file.
- Around line 35-39: The endpoint in AuthController (method getNewToken handling
RefreshTokenRequest) incorrectly returns 201 Created via
ResponseEntity.status(201).body(authService.getNewToken(...)); change it to
return 200 OK instead—use ResponseEntity.ok(...) (or ResponseEntity.status(200))
so the refresh token response from authService.getNewToken(refreshTokenRequest)
is returned with HTTP 200.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 23-26: The response string in MainController.hello() is truncated:
update the method (public String hello()) to return the full word "You are
authenticated" instead of "You are auth"; locate the hello() handler in the
MainController class and replace the returned literal with the corrected
message.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Around line 20-21: RefreshTokenEntity currently persists the full refresh
token string in the field named "token", which stores a replayable credential;
change the entity to store only the JWT ID (jti) by replacing/renaming the field
to "jti" (UUID/String as appropriate) and update all usages: ensure JwtService
(or the component that verifies tokens) extracts the verified JWT ID and
AuthService saves that jti into RefreshTokenEntity instead of the raw token, and
adjust lookup/revocation logic to query by jti rather than token.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 50-55: The handler in GlobalExceptionHandler (method
handleUnauthenticated) incorrectly maps all IllegalArgumentException to 401;
change the exception type handled to the specific auth exception your
AuthService throws (e.g., AuthenticationException or IllegalStateException if
AuthService uses that) so only auth failures return UNAUTHORIZED, and leave
IllegalArgumentException to the bad-request handler; update the
`@ExceptionHandler` annotation and method signature for handleUnauthenticated
accordingly and ensure ErrorCode.UNAUTHORIZED and the log remain.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Around line 9-15: The repository is parameterized with the wrong ID type:
change the interface declaration from JpaRepository<UserEntity, Long> to
JpaRepository<UserEntity, UUID> so inherited CRUD methods use UUID correctly
(update the generic type on UserRepository), and remove the redundant custom
findById(UUID id) method; ensure imports/reference to UUID remain and keep other
methods like findByEmail and findUserEntitiesByUsername untouched.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 42-57: Wrap the JWT processing in JwtAuthenticationFilter in a
try-catch so exceptions from jwtService.extractUsername(...) or
jwtService.isTokenValid(...) are caught and do not propagate as 500s;
specifically, surround the block that calls jwtService.extractUsername(jwt),
customUserDetailsService.loadUserByUsername(email) and
jwtService.isTokenValid(jwt, userDetails) with a catch (Exception e) that logs
the failure (use log.debug or log.warn with e) and simply returns from the
filter method (or proceeds without setting
SecurityContextHolder.getContext().setAuthentication(...)) so Spring Security
will treat the request as unauthenticated and produce the correct 401/403
response.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 32-35: The getAuthorities() in UserDetailsImpl currently returns
an empty list which breaks role checks; update getAuthorities() in class
UserDetailsImpl to map the user's role field into a GrantedAuthority (e.g.,
create a SimpleGrantedAuthority with the role prefixed with "ROLE_" or as your
SecurityConfig expects) and return a list/collection containing that authority;
ensure you reference the user's role getter/field in UserDetailsImpl and add the
necessary import for
org.springframework.security.core.authority.SimpleGrantedAuthority.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Line 53: The refresh token expiry is incorrectly using
jwtService.getJwtExpiration(); update AuthService where
refreshTokenEntity.setExpiresAt(...) is called to use a dedicated refresh
expiration value instead—introduce a configuration property (e.g.,
refreshTokenExpiration) and expose it via JwtService (add
getRefreshTokenExpiration or similar), then call
jwtService.getRefreshTokenExpiration() (or read the config) and apply
Instant.now().plusMillis/plus(...) accordingly; also update JwtService and your
application properties binding to include the new refreshTokenExpiration setting
so access tokens still use getJwtExpiration() and refresh tokens use the new
value.
- Around line 62-64: Remove the non-atomic existsByEmail pre-check in
AuthService (the block calling repository.existsByEmail(request.email())) and
instead perform the save inside a try-catch: call repository.save(...) (or the
current persist method you use) and catch
org.springframework.dao.DataIntegrityViolationException to detect
duplicate-email constraint violations, then throw the same
IllegalStateException("The account exists, try login, please") (or wrap with
that message) so concurrent requests are handled safely; ensure you import
DataIntegrityViolationException and only catch/handle the constraint case
without swallowing other persistence exceptions.
- Around line 89-115: In getNewToken, the existing RefreshTokenEntity retrieved
via refreshTokenRepository.findByToken(...) is left valid, enabling token
replay; after generating the new refresh token you must invalidate the old
one—either delete it via refreshTokenRepository.delete(refreshToken) or set a
"revoked/used" flag on the existing RefreshTokenEntity and save it (e.g.,
refreshToken.setRevoked(true); refreshTokenRepository.save(refreshToken)) before
persisting the new RefreshTokenEntity and returning the AuthResponse; update the
RefreshTokenEntity model and repository if you choose the flag approach.
- Around line 37-43: AuthService.login currently throws different exceptions for
missing users (repository.findByEmail -> NoSuchElementException with "Login not
found...") and wrong passwords (IllegalStateException "Invalid password"), which
enables account enumeration; change the method to always throw the same generic
authentication failure (e.g., new AuthenticationException or
IllegalArgumentException with a single message like "Invalid email or password")
for both the case where repository.findByEmail returns empty and when
passwordEncoder.matches fails—ensure both branches use that same exception type
and message so AuthService.login, repository.findByEmail handling, and
passwordEncoder.matches comparisons do not reveal which check failed.
- Around line 50-55: RefreshToken handling must switch from storing opaque
strings to a JTI-based signed JWT: update the RefreshTokenEntity by replacing
the token field with a jti (UUID) field and use that field everywhere; change
JwtService.generateRefreshToken() to create a signed JWT containing a UUID jti
claim (and return the JWT string for client use); update RefreshTokenRepository
by renaming findByToken(String) to findByJti(String) and adjust its queries; and
modify AuthService where you currently create and save the RefreshTokenEntity
(and where you lookup by token) to first verify the incoming JWT via JwtService,
extract the jti claim (UUID) and store only that jti in RefreshTokenEntity and
use findByJti(...) for lookups.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Around line 64-65: In UploadService (method returning UploadUrlResponse) fix
the local variable name typo: rename presignedUtl to presignedUrl wherever
declared and used (e.g., the line assigning presignedGetObjectRequest.url() and
the return that uses it) so the identifier matches correct spelling and any
other references in the method are updated accordingly.
- Around line 58-59: The code uses the incoming fileName directly when building
the S3 PutObjectRequest
(PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName)), which allows
path-traversal or malicious keys; validate and sanitize fileName in
UploadService before creating the PutObjectRequest: reject or normalize segments
like “..”, remove or encode path separators and control characters, enforce an
allowed character/length regex or replace with a generated safe identifier
(e.g., UUID) and only then pass the sanitized value to
PutObjectRequest.key(...); ensure any validation errors are handled/returned
appropriately.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c8e974c0-723e-452c-96a0-754d444e2e44
📒 Files selected for processing (20)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
Outdated
Show resolved
Hide resolved
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
Outdated
Show resolved
Hide resolved
|
@coderabbitai, make FULL RESCAN of this PR. Make a complete list of all required changes (clickable). Group tasks by priority, give advice on how to fix them, check that SOLID rules are followed, so everything is consistent (there is no desynchronization with the database or anything like that). If there is boilerplate code somewhere, please advise how to remove it. |
🧩 Analysis chain🏁 Script executed: Length of output: 38414
🔍 Full Rescan — SmartJam Auth PR🔴 P1 — Critical / Security1.
|
| Principle | Status | Issue |
|---|---|---|
| S Single Responsibility | CustomUserDetailsService is in config package; UploadService.init() mixes bucket setup with URL generation logic |
|
| O Open/Closed | ✅ | GlobalExceptionHandler is extensible |
| L Liskov Substitution | ❌ | UserDetailsImpl.getUsername() returns display name instead of the login identifier, violating the UserDetails contract |
| I Interface Segregation | ✅ | Interfaces are reasonably small |
| D Dependency Inversion | UploadService directly instantiates S3Client/S3Presigner instead of depending on an injected abstraction |
🗂️ Consolidated Priority Checklist
🔴 P1 — Must Fix Before Merge
-
#1Account enumeration + NoSuchElementException not mapped → 500 leaks stack trace -
#2Old refresh token never deleted on rotation -
#3register()missing@Transactional -
#4Hardcoded MinIO credentials
🟠 P2 — Important Correctness Issues
-
#5JpaRepository<Entity, Long>vs UUID primary key — type mismatch -
#6getAuthorities()always empty — roles not mapped -
#7Refresh token TTL = access token TTL -
#8Non-atomic check-then-save in register -
#9getUsername()returns display name, not email
🟡 P3 — Code Quality
-
#10No validation annotations on DTOs -
#11getNewTokenreturns 201, should be 200 -
#12Duplicate refresh token creation (extract helper) -
#13CustomUserDetailsServicein wrong package -
#14Remove unnecessaryuserDetailsService()bean wrapper -
#15Unsafe cast inJwtService.getSigningKey() -
#16S3 client created per request -
#17@AllArgsConstructor→@RequiredArgsConstructorinAuthService -
#18AvailabilityStatusmisused inAuthResponse -
#19Remove dead commented-out code inUserEntity -
#20UploadControllermissing@RequestMappingprefix -
#21TypopresignedUtl -
#22Twodependencies {}blocks in rootbuild.gradle
✏️ Learnings added
Learnt from: Satlykovs
URL: https://github.com/Satlykovs/SmartJam/pull/2
Timestamp: 2026-03-16T23:56:57.131Z
Learning: In SmartJam (Satlykovs/SmartJam), both UserRepository and RefreshTokenRepository incorrectly extend JpaRepository with Long as the ID type, but both UserEntity and RefreshTokenEntity use UUID as the primary key (GenerationType.UUID). The correct declarations are JpaRepository<UserEntity, UUID> and JpaRepository<RefreshTokenEntity, UUID>.
Learnt from: Satlykovs
URL: https://github.com/Satlykovs/SmartJam/pull/2
Timestamp: 2026-03-16T23:56:57.131Z
Learning: In SmartJam (Satlykovs/SmartJam), UserDetailsImpl.getAuthorities() always returns an empty list — the user's Role enum is never mapped to GrantedAuthority. The fix is to store the role in UserDetailsImpl and return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) from getAuthorities(). Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java.
Learnt from: Satlykovs
URL: https://github.com/Satlykovs/SmartJam/pull/2
Timestamp: 2026-03-16T23:56:57.131Z
Learning: In SmartJam (Satlykovs/SmartJam), the refresh token expiry in AuthService is incorrectly set to jwtService.getJwtExpiration() (the access token TTL). Refresh tokens need a separate, much longer expiration (e.g. 7 days), configured separately as security.jwt.refresh-expiration-time. Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java.
Learnt from: Satlykovs
URL: https://github.com/Satlykovs/SmartJam/pull/2
Timestamp: 2026-03-16T23:56:57.131Z
Learning: In SmartJam (Satlykovs/SmartJam), old refresh tokens are never deleted when a new token is issued in AuthService.getNewToken(). The fix is to call refreshTokenRepository.delete(refreshToken) before saving the new one. Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-04T22:40:29.017Z
Learning: In the SmartJam repository (Satlykovs/SmartJam), Spotless is configured for code formatting. Tests will fail without running the `spotlessApply` Gradle goal first. Contributors should run `spotlessApply` before submitting code. The goal is available in the Gradle tool window in the IDE.
Learnt from: men229
Repo: Satlykovs/SmartJam PR: 2
File: backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java:25-26
Timestamp: 2026-03-16T23:31:31.786Z
Learning: In SmartJam (Satlykovs/SmartJam), UserEntity uses `email` as the unique login identifier (unique = true in Column). The `username`/`nickname` field is a non-unique display name and does not require a uniqueness constraint. Repository methods like `existsByUsername()` / `findUserEntitiesByUsername()` may have been renamed to reflect `nickname`. Do not flag missing uniqueness on the nickname/display-name field. Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java and related repository/service files.
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 2
File: backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java:14-20
Timestamp: 2026-03-12T21:20:36.483Z
Learning: In the SmartJam project, the JTI-based refresh token approach has been confirmed: store only the `jti` (UUID) from the refresh JWT in RefreshTokenEntity.jti (not the full token string). JwtService.generateRefreshToken() should build a signed JWT with a UUID jti claim. RefreshTokenRepository.findByJti(String jti) is used for lookups. AuthService extracts the jti after verifying the JWT signature and stores/validates against it. This applies to all relevant files under backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/.
✅ Actions performed
Full review triggered.
🧩 Analysis chain🏁 Script executed: Length of output: 34080
🔍 Full PR Rescan — Complete Change Checklist🔴 P0 — Critical Bugs / Will Break at Runtime1. 🔗
|
| Principle | Violation | Location |
|---|---|---|
| S — Single Responsibility | AuthService manages auth + token persistence |
AuthService.java |
| S — Single Responsibility | CustomUserDetailsService in config package |
config/ |
| L — Liskov Substitution | getAuthorities() returns empty list — breaks Spring Security contract |
UserDetailsImpl.java |
| D — Dependency Inversion | AuthService, JwtAuthenticationFilter depend on concrete JwtService class |
Both files |
| D — Dependency Inversion | UploadService creates S3 clients directly with hardcoded config |
UploadService.java |
📋 Pre-merge Checklist
- Run
spotlessApply(Gradle goal) before every commit — CI will fail without it - Add
application.propertieswith JWT secret/expiration and MinIO config - Javadoc coverage is at 0% (threshold 80%) — add Javadoc to all public methods
🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-04T22:40:29.017Z
Learning: In the SmartJam repository (Satlykovs/SmartJam), Spotless is configured for code formatting. Tests will fail without running the `spotlessApply` Gradle goal first. Contributors should run `spotlessApply` before submitting code. The goal is available in the Gradle tool window in the IDE.
Learnt from: men229
Repo: Satlykovs/SmartJam PR: 2
File: backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java:25-26
Timestamp: 2026-03-16T23:31:31.786Z
Learning: In SmartJam (Satlykovs/SmartJam), UserEntity uses `email` as the unique login identifier (unique = true in Column). The `username`/`nickname` field is a non-unique display name and does not require a uniqueness constraint. Repository methods like `existsByUsername()` / `findUserEntitiesByUsername()` may have been renamed to reflect `nickname`. Do not flag missing uniqueness on the nickname/display-name field. Applies to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java and related repository/service files.
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 2
File: backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java:14-20
Timestamp: 2026-03-12T21:20:36.483Z
Learning: In the SmartJam project, the JTI-based refresh token approach has been confirmed: store only the `jti` (UUID) from the refresh JWT in RefreshTokenEntity.jti (not the full token string). JwtService.generateRefreshToken() should build a signed JWT with a UUID jti claim. RefreshTokenRepository.findByJti(String jti) is used for lookups. AuthService extracts the jti after verifying the JWT signature and stores/validates against it. This applies to all relevant files under backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/.
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (24)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)
1-3:⚠️ Potential issue | 🟠 MajorAdd Bean Validation constraints to registration fields.
This record currently accepts blank usernames, invalid emails, and short/blank passwords. Add
@NotBlanktousername,@NotBlank@Email`` toSuggested patch
package com.smartjam.smartjamapi.dto; -public record RegisterRequest(String username, String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record RegisterRequest( + `@NotBlank` String username, + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8) String password) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java` around lines 1 - 3, The RegisterRequest record currently allows invalid inputs; update the record declaration to annotate the components: add `@NotBlank` to username, add `@NotBlank` and `@Email` to email, and add `@NotBlank` and `@Size`(min = 8) to password; ensure the class imports the validation annotations (javax/ jakarta.validation.constraints.* consistent with project) so the RegisterRequest(String username, String email, String password) record components are validated on binding.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java (1)
4-8: 🛠️ Refactor suggestion | 🟠 MajorUse an auth-specific status contract here.
AvailabilityStatusis an availability-domain enum, but the auth flow currently usesAVAILABLEto mean “login succeeded”. That makes the response contract harder for clients to interpret. Replace it with an auth-specific enum, or drop the field and rely on HTTP status/token presence.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java` around lines 4 - 8, AuthResponse currently exposes AvailabilityStatus (an availability-domain enum) which conflates availability with auth semantics; either introduce an auth-specific enum (e.g., AuthStatus with values like SUCCESS, FAILURE) and replace the AvailabilityStatus field in AuthResponse with that enum, or remove the status field entirely and rely on HTTP status codes and token presence; update the AuthResponse record signature and all usages (e.g., any controllers/services constructing AuthResponse and any consumers parsing it) to use the new AuthStatus type or to handle the absence of a status field accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java (1)
3-6:⚠️ Potential issue | 🟠 MajorReject blank refresh tokens at the DTO boundary.
This record still has no constraint on
refreshToken, so null/blank payloads can reach token parsing instead of failing fast at validation. Add@NotBlankto the component.Suggested patch
package com.smartjam.smartjamapi.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; public record RefreshTokenRequest( - `@JsonProperty`("refresh_token") String refreshToken) {} + `@NotBlank` `@JsonProperty`("refresh_token") String refreshToken) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java` around lines 3 - 6, The RefreshTokenRequest record allows null/blank refreshToken values; add Bean Validation to fail fast by annotating the record component with `@NotBlank` (i.e., change the component declaration to `@JsonProperty`("refresh_token") `@NotBlank` String refreshToken) and import the correct javax.validation.constraints.NotBlank (or jakarta.validation.constraints.NotBlank) so validation triggers at the DTO boundary (ensure validation is enabled in controller pipeline).backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java (1)
1-3:⚠️ Potential issue | 🟠 MajorAdd Bean Validation constraints to the login DTO.
As written, this record accepts blank or malformed credentials. Add
@NotBlank@Email`` toSuggested patch
package com.smartjam.smartjamapi.dto; -public record LoginRequest(String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record LoginRequest( + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8) String password) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java` around lines 1 - 3, The LoginRequest record currently allows blank or malformed credentials; add Bean Validation annotations to its components by annotating the email component with `@NotBlank` and `@Email` and the password component with `@NotBlank` and `@Size`(min = 8) on the LoginRequest record declaration, and add the corresponding imports for the validation annotations (e.g., jakarta.validation.constraints.NotBlank, Email, Size) so incoming payloads are validated before authentication logic runs.backend/smartjam-api/build.gradle (1)
6-8:⚠️ Potential issue | 🟠 MajorDon't re-pin the JJWT modules individually to
0.12.3.This reintroduces the older JJWT line that was already asked to be upgraded in the PR, and hardcoding three module versions makes skew easy. Restore the BOM-based setup so the JWT artifacts stay aligned on the agreed version line.
Suggested patch
- implementation('io.jsonwebtoken:jjwt-api:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') + implementation(platform('io.jsonwebtoken:jjwt-bom:0.13.0')) + implementation('io.jsonwebtoken:jjwt-api') + runtimeOnly('io.jsonwebtoken:jjwt-impl') + runtimeOnly('io.jsonwebtoken:jjwt-jackson')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/build.gradle` around lines 6 - 8, The three JJWT dependencies were hard-pinned to 0.12.3 (implementation('io.jsonwebtoken:jjwt-api:0.12.3'), runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3'), runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3')), which reintroduces an old line and allows version skew; restore the BOM-based configuration instead: remove the explicit versions from the jjwt-api/jjwt-impl/jjwt-jackson dependency declarations and re-add the project-wide JJWT BOM/platform usage so all io.jsonwebtoken artifacts are resolved from the agreed BOM version line and remain aligned.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java (2)
36-40: 🧹 Nitpick | 🔵 TrivialReuse
SecureRandominstance instead of creating a new one per call.Creating a new
SecureRandominstance for each token generation is inefficient. Make it a class-level constant.Suggested fix
`@Service` public class JwtService { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + // ... fields ... public String generateRefreshToken() { byte[] randomBytes = new byte[64]; - new SecureRandom().nextBytes(randomBytes); + SECURE_RANDOM.nextBytes(randomBytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java` around lines 36 - 40, The generateRefreshToken method currently creates a new SecureRandom per call; define a private static final SecureRandom (e.g., SECURE_RANDOM) at the class level and replace new SecureRandom() in generateRefreshToken with that constant to reuse the instance and avoid repeated construction; ensure the field is initialized once (thread-safe) and used in generateRefreshToken when filling randomBytes.
31-34: 🧹 Nitpick | 🔵 TrivialCache the signing key to avoid repeated decoding.
getSigningKey()decodes the base64 secret and creates a newKeyobject on every call. Cache it at initialization.Suggested fix
+import jakarta.annotation.PostConstruct; + `@Service` public class JwtService { + private Key signingKey; + `@Value`("${security.jwt.secret-key}") private String secretKey; `@Getter` `@Value`("${security.jwt.expiration-time}") private long jwtExpiration; + `@PostConstruct` + public void init() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.signingKey = Keys.hmacShaKeyFor(keyBytes); + } + private Key getSigningKey() { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - return Keys.hmacShaKeyFor(keyBytes); + return signingKey; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java` around lines 31 - 34, getSigningKey() decodes the base64 secret and allocates a new Key on every call; change this by decoding secretKey once and caching the resulting Key in a private final field (e.g. signingKey) during bean initialization (constructor or `@PostConstruct`) and have getSigningKey() simply return that cached Key; update uses of getSigningKey() to rely on the cached field, ensuring the field is immutable and thread-safe.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
28-35:⚠️ Potential issue | 🔴 CriticalAuthorities are always empty, breaking role-based authorization.
getAuthorities()returns an empty list, so all role-based security checks (e.g.,@PreAuthorize("hasRole('TEACHER')")) will fail. Thebuild()method must pass the user's role, andgetAuthorities()must return the mapped authority.🐛 Proposed fix
+import org.springframework.security.core.authority.SimpleGrantedAuthority; +import com.smartjam.smartjamapi.enums.Role; +import java.util.Collections; `@AllArgsConstructor` `@NullMarked` public class UserDetailsImpl implements UserDetails { `@Getter` private UUID id; private String username; `@Getter` private String email; private String password; + private Collection<? extends GrantedAuthority> authorities; public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash()); + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPasswordHash(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) + ); } `@Override` public Collection<? extends GrantedAuthority> getAuthorities() { - return List.of(); + return authorities; }Based on learnings: "The fix is to store the role in UserDetailsImpl and return
List.of(new SimpleGrantedAuthority("ROLE_" + role.name()))from getAuthorities()."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 28 - 35, UserDetailsImpl currently discards the user's role so getAuthorities() returns an empty list; update the UserDetailsImpl class to store the role (add a role field set in the static build(UserEntity user) method using user.getRole()) and change getAuthorities() to return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); ensure imports for SimpleGrantedAuthority and the role type are added and constructors/fields (role) are updated accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)
11-11: 🧹 Nitpick | 🔵 TrivialAlign parameter name with what it represents.
The parameter
logininfindByEmail(String login)is misleading. UseSuggested fix
- Optional<UserEntity> findByEmail(String login); + Optional<UserEntity> findByEmail(String email);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` at line 11, The method parameter name in UserRepository.findByEmail(String login) is misleading; rename the parameter from login to email in the UserRepository interface and update any implementing classes or usages (e.g., repository implementations, tests, or calls) to use the new parameter name so the method signature reads findByEmail(String email) for clarity and consistency with the method purpose.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (3)
64-65:⚠️ Potential issue | 🟡 MinorFix typo:
presignedUtlshould bepresignedUrl.Line 64 has a misspelled variable name.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 64 - 65, Rename the misspelled local variable presignedUtl to presignedUrl in UploadService (the declaration URL presignedUtl = presignedGetObjectRequest.url() and its use in return new UploadUrlResponse(presignedUtl.toString())); update all occurrences in that method to presignedUrl so the variable name is correct and consistent.
25-26:⚠️ Potential issue | 🔴 CriticalDo not hardcode MinIO endpoint and credentials in source.
This leaks secrets into VCS and couples runtime config to local/dev defaults.
🔒 Proposed fix
+import org.springframework.beans.factory.annotation.Value; @@ public class UploadService { @@ - private static final String S3_ENDPOINT = "http://localhost:9000"; // пока захардкодил ссылку + `@Value`("${minio.url}") + private String s3Endpoint; + + `@Value`("${minio.access-key}") + private String accessKey; + + `@Value`("${minio.secret-key}") + private String secretKey; @@ - .endpointOverride(URI.create(S3_ENDPOINT)) + .endpointOverride(URI.create(s3Endpoint)) @@ - AwsBasicCredentials.create("minioadmin", "minioadmin"))) + AwsBasicCredentials.create(accessKey, secretKey))) @@ - .endpointOverride(URI.create(S3_ENDPOINT)) + .endpointOverride(URI.create(s3Endpoint)) @@ - StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) + StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)))Also applies to: 34-36, 50-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 25 - 26, The UploadService currently hardcodes S3_ENDPOINT and BUCKET_NAME (and other MinIO credentials elsewhere) in source; change these to injected configuration values by adding properties (e.g., S3 endpoint, bucket name, access key, secret key) and read them via Spring `@Value` or a `@ConfigurationProperties` class used by UploadService instead of the static constants; ensure no secrets remain in code, provide non-sensitive defaults only in application.properties or application.yaml for local dev, and load real secrets from environment variables or an external secret store so UploadService uses the injected fields (S3_ENDPOINT, BUCKET_NAME and credential fields) at runtime.
58-60:⚠️ Potential issue | 🟠 MajorValidate/sanitize
fileNamebefore using it as object key.Line 59 uses untrusted input directly as key. Reject traversal-like patterns and unexpected characters (or generate a UUID key and keep only extension).
🛡️ Proposed fix
+import java.util.regex.Pattern; @@ public class UploadService { + private static final Pattern SAFE_FILE_NAME = Pattern.compile("^[a-zA-Z0-9._-]{1,255}$"); @@ public UploadUrlResponse generateUploadUrl(String fileName) { + if (fileName == null || !SAFE_FILE_NAME.matcher(fileName).matches()) { + throw new IllegalArgumentException("Invalid fileName"); + } try (S3Presigner presigner = S3Presigner.builder()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 58 - 60, The code uses the untrusted variable fileName directly as the S3 object key when building PutObjectRequest in UploadService (PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName)), so validate/sanitize or replace it before use: implement a check that rejects traversal tokens ("..", "/" or "\"), control characters, and unexpected characters (allow only a safe whitelist like alphanumerics, dashes, underscores and a dot for extension) or instead generate a safe key (e.g., UUID) and preserve the original file extension if present; then use the sanitized/generated key in PutObjectRequest.builder().key(...) and return or persist that safe key rather than the raw fileName.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java (1)
16-19:⚠️ Potential issue | 🟠 MajorRemove the redundant
UserDetailsServicebean to avoid bean ambiguity.
CustomUserDetailsServiceis already a bean; exposing anotherUserDetailsServicehere can lead to ambiguous injection (NoUniqueBeanDefinitionException) in interface-based autowiring paths.♻️ Proposed fix
-import org.springframework.security.core.userdetails.UserDetailsService; @@ -@RequiredArgsConstructor public class ApplicationConfig { - - private final CustomUserDetailsService customUserDetailsService; - - `@Bean` - public UserDetailsService userDetailsService() { - return customUserDetailsService::loadUserByUsername; - } `@Bean` public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }#!/bin/bash # Verify duplicate UserDetailsService providers in repository rg -nP --type=java 'class\s+CustomUserDetailsService|implements\s+UserDetailsService|@Bean\s+public\s+UserDetailsService'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java` around lines 16 - 19, Remove the redundant UserDetailsService bean defined in ApplicationConfig (the method userDetailsService()) because CustomUserDetailsService is already registered as a bean and registering another bean for the same interface causes NoUniqueBeanDefinitionException; delete the userDetailsService() method from ApplicationConfig so injections of UserDetailsService resolve to the existing CustomUserDetailsService bean.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
29-33:⚠️ Potential issue | 🟡 MinorUse proper HTTP statuses: keep
201 Createdfor register, return200 OKfor refresh.Line 32 should use
HttpStatus.CREATEDinstead of literal201, and Line 38 should returnResponseEntity.ok(...).♻️ Proposed fix
+import org.springframework.http.HttpStatus; @@ `@PostMapping`("/register") public ResponseEntity<AuthResponse> register(`@RequestBody` `@Valid` RegisterRequest request) { log.info("Calling register"); - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)); } @@ `@PostMapping`("/refresh") public ResponseEntity<AuthResponse> getNewToken(`@RequestBody` `@Valid` RefreshTokenRequest refreshTokenRequest) { log.info("Calling getNewToken"); - return ResponseEntity.status(201).body(authService.getNewToken(refreshTokenRequest)); + return ResponseEntity.ok(authService.getNewToken(refreshTokenRequest)); }Based on learnings: In Java Spring controllers, token endpoints like
/refreshshould return200 OK, and numeric status literals should be replaced with typed constants.Also applies to: 35-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 29 - 33, The register handler in AuthController currently uses a numeric status literal and the refresh endpoint returns a non-idiomatic response; update the register(...) method to return ResponseEntity.status(HttpStatus.CREATED).body(...) instead of ResponseEntity.status(201) and change the refresh endpoint (the handler for "/refresh") to return ResponseEntity.ok(authService.refresh(...)) (i.e., use ResponseEntity.ok(...) / HttpStatus.OK) so both handlers use proper Spring HttpStatus constants and idiomatic ResponseEntity helpers.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (1)
25-25:⚠️ Potential issue | 🟡 MinorFix response text typo for clarity.
Line 25 returns a truncated message (
"You are auth"). Use a complete phrase like"You are authenticated".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` at line 25, Update the response string returned by the method in MainController that currently returns "You are auth" to a clearer full phrase; locate the method in class MainController (the handler returning that string) and replace the returned literal with "You are authenticated" so the endpoint response is grammatically correct and unambiguous.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (2)
45-45: 🧹 Nitpick | 🔵 TrivialUse
log.debuginstead oflog.infofor per-request logging.Logging every authenticated request at INFO level creates noise in production logs. Reserve INFO for significant events.
- log.info("Filter for {}", email); + log.debug("Processing JWT authentication for: {}", email);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` at line 45, Change the per-request logging in JwtAuthenticationFilter so it uses debug level instead of info: locate the log statement in JwtAuthenticationFilter that currently logs "Filter for {}" (log.info("Filter for {}", email)) and switch it to log.debug(...) to reduce noise in production logs while retaining the message for troubleshooting.
42-57:⚠️ Potential issue | 🟠 MajorUnhandled JWT exceptions will propagate as 500 errors.
jwtService.extractUsername(jwt)andjwtService.isTokenValid()call into jjwt's parser, which throwsExpiredJwtException,MalformedJwtException,SignatureException, etc. on invalid tokens. Without a try-catch, these propagate as 500 Internal Server Error instead of 401 Unauthorized.🔧 Proposed fix (try-catch approach)
final String jwt = authHeader.substring(7); - final String email = jwtService.extractUsername(jwt); - - log.info("Filter for {}", email); - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); - - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authToken); + try { + final String email = jwtService.extractUsername(jwt); + + log.debug("Processing JWT authentication for: {}", email); + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authToken); + } } + } catch (io.jsonwebtoken.JwtException | IllegalArgumentException ex) { + log.warn("Invalid JWT token: {}", ex.getMessage()); + // Don't set authentication - Spring Security will reject as unauthorized }Consider also implementing a custom
AuthenticationEntryPointfor consistent 401 JSON responses, as discussed in previous review comments.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 42 - 57, Wrap the JWT parsing/validation in JwtAuthenticationFilter around a try-catch to prevent jjwt exceptions from bubbling up: surround calls to jwtService.extractUsername(jwt) and jwtService.isTokenValid(jwt, userDetails) with a catch for io.jsonwebtoken.JwtException (or specific ExpiredJwtException, MalformedJwtException, SignatureException) and on catch log the error and stop filter processing (do not set SecurityContext). Optionally invoke your AuthenticationEntryPoint (or set response status to 401) so the client gets a consistent 401 JSON response; also ensure UsernamePasswordAuthenticationToken construction and SecurityContextHolder.setAuthentication(...) only occur when no exception was thrown.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (6)
25-27: 🧹 Nitpick | 🔵 Trivial
@Slf4jadded but no logging after successful operations.The class now has
@Slf4jbut doesn't log successful authentication events. Add logging after successful login, registration, and token refresh for audit trails.📝 Example additions
refreshTokenRepository.save(refreshTokenEntity); + log.info("User logged in successfully: {}", userEntity.getEmail()); return new AuthResponse("Logged in successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken);refreshTokenRepository.save(refreshTokenEntity); + log.info("User registered successfully: {}", userEntity.getEmail()); return new AuthResponse("Register successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken);refreshTokenRepository.save(refreshTokenEntity); + log.info("Token refreshed for user: {}", userEntity.getEmail()); return new AuthResponse(Also applies to: 57-57, 86-86, 114-114
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 25 - 27, AuthService has `@Slf4j` but does not log successful operations; add info-level log statements in the methods that complete successfully—specifically in authenticate (successful login), register (successful user creation), and refreshToken (successful token refresh) within class AuthService—using descriptive messages that include non-sensitive identifiers (e.g., username or userId) and operation context, e.g., log.info("User {} authenticated successfully", username) or similar for register and refreshToken to provide audit trails without logging secrets.
66-72: 🧹 Nitpick | 🔵 TrivialUse builder pattern for entity creation.
Manual setter chains are verbose and error-prone. Use Lombok's
@Builderon entities to simplify construction.♻️ Example with builder
- RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); - refreshTokenEntity.setToken(refreshToken); - refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getRefreshExpiration())); + RefreshTokenEntity refreshTokenEntity = RefreshTokenEntity.builder() + .token(refreshToken) + .user(userEntity) + .expiresAt(Instant.now().plusMillis(jwtService.getRefreshExpiration())) + .build();Also applies to: 79-82, 106-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 66 - 72, Replace the manual setter-based construction of UserEntity with Lombok's builder pattern: ensure the UserEntity class is annotated with `@Builder/`@AllArgsConstructor (if not already), then create the instance via UserEntity.builder().username(request.username()).email(request.email()).passwordHash(passwordEncoder.encode(request.password())).role(Role.STUDENT).build() and pass that to repository.save; apply the same builder-style replacement for the other similar constructions around the register/login flows (the other UserEntity creation sites referenced) to reduce verbosity and avoid setter chains.
53-53:⚠️ Potential issue | 🟠 MajorRefresh token expiry incorrectly uses access token TTL.
jwtService.getJwtExpiration()is the access token lifetime (typically minutes). Refresh tokens require a separate, much longer expiration (e.g., 7 days). Introduce a dedicated configuration likesecurity.jwt.refresh-expiration-time.Based on learnings: "In SmartJam, the refresh token expiry in AuthService is incorrectly set to jwtService.getJwtExpiration() (the access token TTL). Refresh tokens need a separate, much longer expiration."
Also applies to: 82-82, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 53, The refresh token expiry in AuthService is being set using jwtService.getJwtExpiration() (the access token TTL); change this to use a dedicated refresh-token TTL configuration (e.g., security.jwt.refresh-expiration-time) by adding a new property and accessor (or a jwtService.getRefreshExpiration() method), and replace the three occurrences of refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())) in AuthService with Instant.now().plusMillis(<refresh-expiration-ms>) so refresh tokens use the longer configured TTL; also add the new property to your configuration class and application config (yml/properties).
60-64:⚠️ Potential issue | 🟠 Major
register()is missing@Transactionaland has a race condition.
- The method performs multiple DB operations (user save + refresh token save) without
@Transactional, risking inconsistent state on failure.- The check-then-save pattern (
existsByEmail()→save()) is not atomic; concurrent requests can both pass the check and cause a constraint violation.🔧 Proposed fix
+ `@Transactional` public AuthResponse register(RegisterRequest request) { - - if (repository.existsByEmail(request.email())) { - throw new IllegalStateException("The account exists, try login, please"); - } - - UserEntity userEntity = new UserEntity(); - userEntity.setUsername(request.username()); - userEntity.setEmail(request.email()); - userEntity.setPasswordHash(passwordEncoder.encode(request.password())); - userEntity.setRole(Role.STUDENT); - - repository.save(userEntity); + UserEntity userEntity = new UserEntity(); + userEntity.setUsername(request.username()); + userEntity.setEmail(request.email()); + userEntity.setPasswordHash(passwordEncoder.encode(request.password())); + userEntity.setRole(Role.STUDENT); + + try { + repository.save(userEntity); + } catch (org.springframework.dao.DataIntegrityViolationException ex) { + throw new IllegalStateException("The account exists, try login, please"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 60 - 64, The register() method in AuthService currently uses a check-then-save flow (repository.existsByEmail(...) followed by save operations including persisting a refresh token) without a transaction, causing race conditions and possible partial persistence; mark the method with `@Transactional` and remove the non-atomic pre-check by relying on the database unique constraint for the email, then catch and translate the persistence exception (e.g., DataIntegrityViolationException or ConstraintViolationException) to a clear application exception; ensure the user save and refresh token save happen within the same transactional method (register) so failures roll back both operations.
37-43:⚠️ Potential issue | 🟠 MajorAccount enumeration vulnerability remains unaddressed.
Lines 40 and 42 still expose different failure reasons (
"Login not found"vs"Invalid password"), enabling attackers to enumerate valid accounts. Return a single generic error for both cases.🔒 Proposed fix
`@Transactional` public AuthResponse login(LoginRequest request) { UserEntity userEntity = repository .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + .orElseThrow(() -> new IllegalStateException("Invalid credentials")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { - throw new IllegalStateException("Invalid password"); + throw new IllegalStateException("Invalid credentials"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 37 - 43, The login method in AuthService currently leaks account existence via different errors from repository.findByEmail(...) and passwordEncoder.matches(...); change AuthService.login to always throw the same generic authentication exception/message for both "user not found" and "invalid password" cases (e.g., throw new AuthenticationException or IllegalStateException with a single message like "Invalid credentials") and ensure both failure paths use that same exception and message (keep usage of repository.findByEmail and passwordEncoder.matches but do not reveal which check failed).
89-111:⚠️ Potential issue | 🟠 MajorOld refresh token is not invalidated after use.
When issuing a new refresh token in
getNewToken(), the oldRefreshTokenEntityremains valid in the database. This allows refresh token replay attacks. Delete the old token before saving the new one.🔒 Proposed fix
String accessToken = jwtService.generateAccessToken(userDetails); String newRefreshToken = jwtService.generateRefreshToken(); + // Invalidate old refresh token + refreshTokenRepository.delete(refreshToken); + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); refreshTokenEntity.setToken(newRefreshToken);Based on learnings: "In SmartJam, old refresh tokens are never deleted when a new token is issued in AuthService.getNewToken(). The fix is to call refreshTokenRepository.delete(refreshToken) before saving the new one."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 89 - 111, getNewToken leaves the previous RefreshTokenEntity valid allowing replay; before saving the newly-created RefreshTokenEntity you should invalidate the old one by deleting it from the repository. In the AuthService.getNewToken method, after creating/verifying the existing RefreshTokenEntity (variable refreshToken) and before calling refreshTokenRepository.save(refreshTokenEntity), call refreshTokenRepository.delete(refreshToken) (or equivalent repository removal) to remove the old token.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (1)
36-36: 🧹 Nitpick | 🔵 TrivialFix method naming: use "handle" instead of "handler".
Method names
handlerGenericException,handlerEntityNotFound, andhandlerBadRequestshould be verbs. This was previously flagged and marked as addressed but remains unfixed.✏️ Proposed renames
- public ResponseEntity<ErrorResponseDto> handlerGenericException(Exception e) { + public ResponseEntity<ErrorResponseDto> handleGenericException(Exception e) { - public ResponseEntity<ErrorResponseDto> handlerEntityNotFound(EntityNotFoundException e) { + public ResponseEntity<ErrorResponseDto> handleEntityNotFound(EntityNotFoundException e) { - public ResponseEntity<ErrorResponseDto> handlerBadRequest(Exception e) { + public ResponseEntity<ErrorResponseDto> handleBadRequest(Exception e) {Also applies to: 44-44, 58-58
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` at line 36, Rename the misnamed exception handler methods to use the verb "handle" prefix: change handlerGenericException to handleGenericException, handlerEntityNotFound to handleEntityNotFound, and handlerBadRequest to handleBadRequest; update all references/usages (annotations, method calls, tests) to the new names so method signatures and any `@ExceptionHandler` annotations on these methods (e.g., the methods currently named handlerGenericException, handlerEntityNotFound, handlerBadRequest in GlobalExceptionHandler) remain wired correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 13-26: CustomUserDetailsService is a service class located in the
config package; move the class file to a more appropriate package such as
com.smartjam.smartjamapi.service or com.smartjam.smartjamapi.security, update
the package declaration at the top of CustomUserDetailsService, and adjust any
imports or references (e.g., UserRepository, UserDetailsImpl) and tests/beans
that reference the old package so component scanning still picks up the
`@Service-annotated` class.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 17-20: The controller currently logs the raw principal identifier
at info level (log.info(principal.getName())) in MainController; change this to
a lower verbosity level (debug or trace) and avoid logging the raw identifier by
masking or redacting it (e.g., replace with a hashed/partial value or fixed
placeholder) before logging, and update the log message to not include PII while
keeping the existing log statement that "Call userAccess" intact.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Around line 20-21: Rename the field token to tokenHash in RefreshTokenEntity
(update the private field name and its getter/setter) to reflect it stores a
hash; update any JPA annotations if necessary (keep `@Column`(nullable = false,
unique = true) on tokenHash). Also rename
RefreshTokenRepository.findByToken(...) to findByTokenHash(...) and update all
call sites in AuthService (and any other classes) to use findByTokenHash and the
new entity getter/setter names so references align with the renamed field and
method.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Around line 50-59: Remove the commented-out dead UserDetails methods from
UserEntity: delete the commented blocks for getAuthorities() and getPassword()
(the lines referencing SimpleGrantedAuthority, ROLE_ + role.name(), and
passwordHash) since UserEntity no longer implements UserDetails; ensure no other
references to those methods remain and run a quick compile to confirm clean
build.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 50-62: The current exception-to-status mapping is inverted: change
the `@ExceptionHandler` on handleUnauthenticated to handle IllegalStateException
(so handleUnauthenticated(IllegalStateException e) returns
HttpStatus.UNAUTHORIZED) and change the `@ExceptionHandler` on handlerBadRequest
to handle IllegalArgumentException (in addition to
MethodArgumentNotValidException) so handlerBadRequest(Exception e) returns
HttpStatus.BAD_REQUEST; optionally consider replacing IllegalStateException use
in AuthService with a dedicated AuthenticationFailedException and update
handleUnauthenticated to handle that custom exception instead.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`:
- Around line 8-9: The repository uses the wrong ID type: update
RefreshTokenRepository to extend JpaRepository<RefreshTokenEntity, UUID>
(replace the current Long) so inherited methods like findById, deleteById,
existsById work with the entity's UUID primary key; ensure you import
java.util.UUID and keep the existing method Optional<RefreshTokenEntity>
findByToken(String token) unchanged.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Around line 9-13: The repository generic ID type is wrong: change
UserRepository to extend JpaRepository<UserEntity, UUID> (replace Long with
UUID) so all inherited methods (deleteById, existsById, getReferenceById, etc.)
use UUID; remove or keep the explicit Optional<UserEntity> findById(UUID id) if
desired but it will be redundant once the interface generic is corrected; also
audit and apply the same fix to RefreshTokenRepository so RefreshTokenRepository
extends JpaRepository<RefreshTokenEntity, UUID> and import java.util.UUID where
needed.
---
Duplicate comments:
In `@backend/smartjam-api/build.gradle`:
- Around line 6-8: The three JJWT dependencies were hard-pinned to 0.12.3
(implementation('io.jsonwebtoken:jjwt-api:0.12.3'),
runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3'),
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3')), which reintroduces an old
line and allows version skew; restore the BOM-based configuration instead:
remove the explicit versions from the jjwt-api/jjwt-impl/jjwt-jackson dependency
declarations and re-add the project-wide JJWT BOM/platform usage so all
io.jsonwebtoken artifacts are resolved from the agreed BOM version line and
remain aligned.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java`:
- Around line 16-19: Remove the redundant UserDetailsService bean defined in
ApplicationConfig (the method userDetailsService()) because
CustomUserDetailsService is already registered as a bean and registering another
bean for the same interface causes NoUniqueBeanDefinitionException; delete the
userDetailsService() method from ApplicationConfig so injections of
UserDetailsService resolve to the existing CustomUserDetailsService bean.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Around line 29-33: The register handler in AuthController currently uses a
numeric status literal and the refresh endpoint returns a non-idiomatic
response; update the register(...) method to return
ResponseEntity.status(HttpStatus.CREATED).body(...) instead of
ResponseEntity.status(201) and change the refresh endpoint (the handler for
"/refresh") to return ResponseEntity.ok(authService.refresh(...)) (i.e., use
ResponseEntity.ok(...) / HttpStatus.OK) so both handlers use proper Spring
HttpStatus constants and idiomatic ResponseEntity helpers.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Line 25: Update the response string returned by the method in MainController
that currently returns "You are auth" to a clearer full phrase; locate the
method in class MainController (the handler returning that string) and replace
the returned literal with "You are authenticated" so the endpoint response is
grammatically correct and unambiguous.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java`:
- Around line 4-8: AuthResponse currently exposes AvailabilityStatus (an
availability-domain enum) which conflates availability with auth semantics;
either introduce an auth-specific enum (e.g., AuthStatus with values like
SUCCESS, FAILURE) and replace the AvailabilityStatus field in AuthResponse with
that enum, or remove the status field entirely and rely on HTTP status codes and
token presence; update the AuthResponse record signature and all usages (e.g.,
any controllers/services constructing AuthResponse and any consumers parsing it)
to use the new AuthStatus type or to handle the absence of a status field
accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java`:
- Around line 1-3: The LoginRequest record currently allows blank or malformed
credentials; add Bean Validation annotations to its components by annotating the
email component with `@NotBlank` and `@Email` and the password component with
`@NotBlank` and `@Size`(min = 8) on the LoginRequest record declaration, and add the
corresponding imports for the validation annotations (e.g.,
jakarta.validation.constraints.NotBlank, Email, Size) so incoming payloads are
validated before authentication logic runs.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java`:
- Around line 3-6: The RefreshTokenRequest record allows null/blank refreshToken
values; add Bean Validation to fail fast by annotating the record component with
`@NotBlank` (i.e., change the component declaration to
`@JsonProperty`("refresh_token") `@NotBlank` String refreshToken) and import the
correct javax.validation.constraints.NotBlank (or
jakarta.validation.constraints.NotBlank) so validation triggers at the DTO
boundary (ensure validation is enabled in controller pipeline).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`:
- Around line 1-3: The RegisterRequest record currently allows invalid inputs;
update the record declaration to annotate the components: add `@NotBlank` to
username, add `@NotBlank` and `@Email` to email, and add `@NotBlank` and `@Size`(min =
8) to password; ensure the class imports the validation annotations (javax/
jakarta.validation.constraints.* consistent with project) so the
RegisterRequest(String username, String email, String password) record
components are validated on binding.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Line 36: Rename the misnamed exception handler methods to use the verb
"handle" prefix: change handlerGenericException to handleGenericException,
handlerEntityNotFound to handleEntityNotFound, and handlerBadRequest to
handleBadRequest; update all references/usages (annotations, method calls,
tests) to the new names so method signatures and any `@ExceptionHandler`
annotations on these methods (e.g., the methods currently named
handlerGenericException, handlerEntityNotFound, handlerBadRequest in
GlobalExceptionHandler) remain wired correctly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Line 11: The method parameter name in UserRepository.findByEmail(String login)
is misleading; rename the parameter from login to email in the UserRepository
interface and update any implementing classes or usages (e.g., repository
implementations, tests, or calls) to use the new parameter name so the method
signature reads findByEmail(String email) for clarity and consistency with the
method purpose.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Line 45: Change the per-request logging in JwtAuthenticationFilter so it uses
debug level instead of info: locate the log statement in JwtAuthenticationFilter
that currently logs "Filter for {}" (log.info("Filter for {}", email)) and
switch it to log.debug(...) to reduce noise in production logs while retaining
the message for troubleshooting.
- Around line 42-57: Wrap the JWT parsing/validation in JwtAuthenticationFilter
around a try-catch to prevent jjwt exceptions from bubbling up: surround calls
to jwtService.extractUsername(jwt) and jwtService.isTokenValid(jwt, userDetails)
with a catch for io.jsonwebtoken.JwtException (or specific ExpiredJwtException,
MalformedJwtException, SignatureException) and on catch log the error and stop
filter processing (do not set SecurityContext). Optionally invoke your
AuthenticationEntryPoint (or set response status to 401) so the client gets a
consistent 401 JSON response; also ensure UsernamePasswordAuthenticationToken
construction and SecurityContextHolder.setAuthentication(...) only occur when no
exception was thrown.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java`:
- Around line 36-40: The generateRefreshToken method currently creates a new
SecureRandom per call; define a private static final SecureRandom (e.g.,
SECURE_RANDOM) at the class level and replace new SecureRandom() in
generateRefreshToken with that constant to reuse the instance and avoid repeated
construction; ensure the field is initialized once (thread-safe) and used in
generateRefreshToken when filling randomBytes.
- Around line 31-34: getSigningKey() decodes the base64 secret and allocates a
new Key on every call; change this by decoding secretKey once and caching the
resulting Key in a private final field (e.g. signingKey) during bean
initialization (constructor or `@PostConstruct`) and have getSigningKey() simply
return that cached Key; update uses of getSigningKey() to rely on the cached
field, ensuring the field is immutable and thread-safe.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 28-35: UserDetailsImpl currently discards the user's role so
getAuthorities() returns an empty list; update the UserDetailsImpl class to
store the role (add a role field set in the static build(UserEntity user) method
using user.getRole()) and change getAuthorities() to return List.of(new
SimpleGrantedAuthority("ROLE_" + role.name())); ensure imports for
SimpleGrantedAuthority and the role type are added and constructors/fields
(role) are updated accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 25-27: AuthService has `@Slf4j` but does not log successful
operations; add info-level log statements in the methods that complete
successfully—specifically in authenticate (successful login), register
(successful user creation), and refreshToken (successful token refresh) within
class AuthService—using descriptive messages that include non-sensitive
identifiers (e.g., username or userId) and operation context, e.g.,
log.info("User {} authenticated successfully", username) or similar for register
and refreshToken to provide audit trails without logging secrets.
- Around line 66-72: Replace the manual setter-based construction of UserEntity
with Lombok's builder pattern: ensure the UserEntity class is annotated with
`@Builder/`@AllArgsConstructor (if not already), then create the instance via
UserEntity.builder().username(request.username()).email(request.email()).passwordHash(passwordEncoder.encode(request.password())).role(Role.STUDENT).build()
and pass that to repository.save; apply the same builder-style replacement for
the other similar constructions around the register/login flows (the other
UserEntity creation sites referenced) to reduce verbosity and avoid setter
chains.
- Line 53: The refresh token expiry in AuthService is being set using
jwtService.getJwtExpiration() (the access token TTL); change this to use a
dedicated refresh-token TTL configuration (e.g.,
security.jwt.refresh-expiration-time) by adding a new property and accessor (or
a jwtService.getRefreshExpiration() method), and replace the three occurrences
of
refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration()))
in AuthService with Instant.now().plusMillis(<refresh-expiration-ms>) so refresh
tokens use the longer configured TTL; also add the new property to your
configuration class and application config (yml/properties).
- Around line 60-64: The register() method in AuthService currently uses a
check-then-save flow (repository.existsByEmail(...) followed by save operations
including persisting a refresh token) without a transaction, causing race
conditions and possible partial persistence; mark the method with `@Transactional`
and remove the non-atomic pre-check by relying on the database unique constraint
for the email, then catch and translate the persistence exception (e.g.,
DataIntegrityViolationException or ConstraintViolationException) to a clear
application exception; ensure the user save and refresh token save happen within
the same transactional method (register) so failures roll back both operations.
- Around line 37-43: The login method in AuthService currently leaks account
existence via different errors from repository.findByEmail(...) and
passwordEncoder.matches(...); change AuthService.login to always throw the same
generic authentication exception/message for both "user not found" and "invalid
password" cases (e.g., throw new AuthenticationException or
IllegalStateException with a single message like "Invalid credentials") and
ensure both failure paths use that same exception and message (keep usage of
repository.findByEmail and passwordEncoder.matches but do not reveal which check
failed).
- Around line 89-111: getNewToken leaves the previous RefreshTokenEntity valid
allowing replay; before saving the newly-created RefreshTokenEntity you should
invalidate the old one by deleting it from the repository. In the
AuthService.getNewToken method, after creating/verifying the existing
RefreshTokenEntity (variable refreshToken) and before calling
refreshTokenRepository.save(refreshTokenEntity), call
refreshTokenRepository.delete(refreshToken) (or equivalent repository removal)
to remove the old token.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Around line 64-65: Rename the misspelled local variable presignedUtl to
presignedUrl in UploadService (the declaration URL presignedUtl =
presignedGetObjectRequest.url() and its use in return new
UploadUrlResponse(presignedUtl.toString())); update all occurrences in that
method to presignedUrl so the variable name is correct and consistent.
- Around line 25-26: The UploadService currently hardcodes S3_ENDPOINT and
BUCKET_NAME (and other MinIO credentials elsewhere) in source; change these to
injected configuration values by adding properties (e.g., S3 endpoint, bucket
name, access key, secret key) and read them via Spring `@Value` or a
`@ConfigurationProperties` class used by UploadService instead of the static
constants; ensure no secrets remain in code, provide non-sensitive defaults only
in application.properties or application.yaml for local dev, and load real
secrets from environment variables or an external secret store so UploadService
uses the injected fields (S3_ENDPOINT, BUCKET_NAME and credential fields) at
runtime.
- Around line 58-60: The code uses the untrusted variable fileName directly as
the S3 object key when building PutObjectRequest in UploadService
(PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName)), so
validate/sanitize or replace it before use: implement a check that rejects
traversal tokens ("..", "/" or "\"), control characters, and unexpected
characters (allow only a safe whitelist like alphanumerics, dashes, underscores
and a dot for extension) or instead generate a safe key (e.g., UUID) and
preserve the original file extension if present; then use the
sanitized/generated key in PutObjectRequest.builder().key(...) and return or
persist that safe key rather than the raw fileName.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4aa7a8f3-0b0e-44de-afd6-ad2af8892c5a
📒 Files selected for processing (28)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.javabackend/smartjam-api/src/main/resources/application.yaml
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
Outdated
Show resolved
Hide resolved
| // @Override | ||
| // public @NonNull Collection<? extends GrantedAuthority> getAuthorities() { | ||
| // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); | ||
| // } | ||
| // | ||
| // @Override | ||
| // public @NonNull String getPassword() { | ||
| // return passwordHash; | ||
| // } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove commented-out dead code.
The commented-out UserDetails methods are no longer needed since UserEntity no longer implements the interface. Dead code should be removed to maintain code cleanliness.
🧹 Proposed cleanup
`@Column`(name = "fcm_token")
private String fcmToken;
-
- // `@Override`
- // public `@NonNull` Collection<? extends GrantedAuthority> getAuthorities() {
- // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
- // }
- //
- // `@Override`
- // public `@NonNull` String getPassword() {
- // return passwordHash;
- // }
}📝 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.
| // @Override | |
| // public @NonNull Collection<? extends GrantedAuthority> getAuthorities() { | |
| // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); | |
| // } | |
| // | |
| // @Override | |
| // public @NonNull String getPassword() { | |
| // return passwordHash; | |
| // } | |
| } | |
| `@Column`(name = "fcm_token") | |
| private String fcmToken; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`
around lines 50 - 59, Remove the commented-out dead UserDetails methods from
UserEntity: delete the commented blocks for getAuthorities() and getPassword()
(the lines referencing SimpleGrantedAuthority, ROLE_ + role.name(), and
passwordHash) since UserEntity no longer implements UserDetails; ensure no other
references to those methods remain and run a quick compile to confirm clean
build.
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Outdated
Show resolved
Hide resolved
...d/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (28)
backend/smartjam-api/build.gradle (1)
6-8:⚠️ Potential issue | 🟠 MajorUpgrade JJWT line and switch to BOM-managed coordinates.
Line 6, Line 7, and Line 8 pin
0.12.3. For auth/security paths, keep JJWT on the current maintained release line and avoid per-module version drift by importing the BOM.Proposed Gradle change
- implementation('io.jsonwebtoken:jjwt-api:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') + implementation(platform('io.jsonwebtoken:jjwt-bom:0.13.0')) + implementation('io.jsonwebtoken:jjwt-api') + runtimeOnly('io.jsonwebtoken:jjwt-impl') + runtimeOnly('io.jsonwebtoken:jjwt-jackson')What is the latest stable version of io.jsonwebtoken:jjwt-bom on Maven Central, and what are the official Gradle dependency coordinates for jjwt-api, jjwt-impl, and jjwt-jackson when using the BOM?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/build.gradle` around lines 6 - 8, Replace the hard-coded per-module versions with the JJWT BOM: add the BOM as a platform dependency using implementation(platform("io.jsonwebtoken:jjwt-bom:<latest-version>")) and then remove versions from the modules so they become implementation("io.jsonwebtoken:jjwt-api"), runtimeOnly("io.jsonwebtoken:jjwt-impl"), and runtimeOnly("io.jsonwebtoken:jjwt-jackson"); update the BOM version to the latest stable release from Maven Central before committing.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java (1)
50-58: 🧹 Nitpick | 🔵 TrivialDelete the commented-out
UserDetailsmethods.This block suggests
UserEntitystill owns the security contract, but the active implementation lives inUserDetailsImpl. Keeping dead commented code here makes future drift more likely.Suggested cleanup
- // `@Override` - // public `@NonNull` Collection<? extends GrantedAuthority> getAuthorities() { - // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); - // } - // - // `@Override` - // public `@NonNull` String getPassword() { - // return passwordHash; - // }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java` around lines 50 - 58, Remove the dead commented-out UserDetails methods from UserEntity: delete the commented getAuthorities and getPassword blocks so the security contract lives only in UserDetailsImpl; ensure no other commented UserDetails fragments remain in class UserEntity and run a quick compile to confirm UserDetailsImpl still provides the required methods.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)
1-3:⚠️ Potential issue | 🟠 MajorPut Bean Validation on the record components.
As written, controller-level
@Validstill won't reject blank usernames, malformed emails, or empty passwords. Those checks need to live on theRegisterRequestcomponents themselves.Suggested patch
package com.smartjam.smartjamapi.dto; -public record RegisterRequest(String username, String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record RegisterRequest( + `@NotBlank` String username, + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8) String password) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java` around lines 1 - 3, Add Bean Validation annotations to the RegisterRequest record components so controller-level `@Valid` can enforce constraints: annotate the username component in the RegisterRequest record to reject blank values (e.g., `@NotBlank`), annotate the email component to validate format and non-blank (e.g., `@Email` and `@NotBlank`), and annotate the password component to reject blank/empty values (e.g., `@NotBlank` and any size constraints like `@Size`(min=...)). Update the record declaration for RegisterRequest(String username, String email, String password) to include these annotations on the respective components.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (3)
48-60:⚠️ Potential issue | 🟠 MajorGenerate the object key server-side instead of trusting
fileName.Line 59 uses the request's
fileNameas the final bucket key. That lets callers collide with existing keys and control your bucket namespace directly. Generate a UUID/ULID key on the server and keep the original filename only as metadata or a sanitized suffix if you still need it.Suggested direction
- PutObjectRequest putObjectRequest = - PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName).build(); + String objectKey = UUID.randomUUID() + extractSafeExtension(fileName); + PutObjectRequest putObjectRequest = + PutObjectRequest.builder().bucket(BUCKET_NAME).key(objectKey).build();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 48 - 60, generateUploadUrl currently uses the client-supplied fileName as the S3 object key, allowing namespace collisions and abuse; modify generateUploadUrl to generate a server-side unique key (e.g., UUID/ULID) and use that key when building the PutObjectRequest (and any presigned URL), while storing the original fileName only as object metadata or as a sanitized suffix if needed; update references to PutObjectRequest.builder().key(...) and any downstream code that returns or persists the object key so they return the generated key instead of the incoming fileName.
30-35: 🧹 Nitpick | 🔵 TrivialInject a shared
S3Presignerinstead of rebuilding it per request.The presigner configuration is duplicated with
init(), and recreating it on every call adds avoidable setup cost on a hot path. Build the S3 client/presigner once as Spring beans from the same config and inject them here.Also applies to: 49-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 30 - 35, The UploadService currently rebuilds S3Client/S3Presigner per request (see S3Client builder and the repeated presigner setup in init()), which duplicates config and is costly; instead create Spring `@Bean`(s) that construct a shared S3Client and S3Presigner (configured with S3_ENDPOINT, Region.US_EAST_1, pathStyleAccessEnabled, and StaticCredentialsProvider) and inject those beans into UploadService (use constructor injection), then remove the per-request builders in methods that call S3Client/S3Presigner so the service uses the injected instances.
25-35:⚠️ Potential issue | 🔴 CriticalMove the MinIO endpoint and credentials out of source control.
S3_ENDPOINTand bothAwsBasicCredentials.create("minioadmin", "minioadmin")calls are hardcoded here. That leaks deploy-time secrets into the repo and makes rotation/environment changes painful. Inject them from configuration once and reuse the same values for both the client and the presigner.Also applies to: 49-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 25 - 35, Replace hardcoded MinIO endpoint and credentials by reading them from configuration and reuse those values for both the S3Client and the Presigner: add injectable fields (e.g., String s3Endpoint, String s3AccessKey, String s3SecretKey) populated from application properties/env, update the init() method to build AwsBasicCredentials from s3AccessKey/s3SecretKey and to call URI.create(s3Endpoint) for endpointOverride, and ensure the same credential provider instance is passed to both S3Client.builder() and S3Presigner.builder() (referencing S3Client, S3Presigner, init(), S3_ENDPOINT, and AwsBasicCredentials.create) so no secrets remain hardcoded in source.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java (1)
3-6:⚠️ Potential issue | 🟠 MajorAdd
@NotBlanktorefreshToken.
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java, Lines 35-39 already use@Valid, but this DTO has no constraint, sonull/blank refresh tokens still reach the service layer instead of failing fast with 400.Suggested patch
import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; public record RefreshTokenRequest( - `@JsonProperty`("refresh_token") String refreshToken) {} + `@NotBlank` + `@JsonProperty`("refresh_token") String refreshToken) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java` around lines 3 - 6, Add a validation constraint to the RefreshTokenRequest record so blank or null refresh tokens fail fast: annotate the record component refreshToken with `@NotBlank` (and add the corresponding import, e.g., jakarta.validation.constraints.NotBlank) so that the existing `@Valid` usage in AuthController triggers a 400 on invalid input; update the import list to include NotBlank and leave the record signature public record RefreshTokenRequest(`@JsonProperty`("refresh_token") `@NotBlank` String refreshToken) {}.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java (1)
20-21:⚠️ Potential issue | 🟠 MajorDo not persist the bearer refresh token in this column.
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java, Lines 89-115 currently writenewRefreshTokenstraight into this field and look it up withfindByToken(...). If the DB leaks, those rows are immediately replayable. This entity should store a one-way digest/reference instead.Suggested patch
- private String token; + private String tokenHash;Update the repository/service flow to compare hashes instead of raw tokens.
Based on learnings, in the SmartJam project the accepted design is to store a hash of the opaque refresh token string inRefreshTokenEntity, not the full token.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java` around lines 20 - 21, The refresh token column currently stores the raw bearer token (RefreshTokenEntity.token) and AuthService writes newRefreshToken and looks it up via findByToken(...); change this to store a one-way digest instead: when issuing a new refresh token in AuthService, compute a secure hash (e.g., SHA-256) of newRefreshToken and persist that hash into RefreshTokenEntity.token, and change lookup logic from findByToken(rawToken) to compute the same hash and query by the hash (or add a method findByTokenHash(...)); ensure comparisons use a constant-time comparison for equality and update any tests/creation code paths referencing raw token storage to use the hashed value for persistence while still returning the opaque raw token to clients.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (2)
9-14:⚠️ Potential issue | 🔴 CriticalFix
JpaRepositoryID type and remove shadowfindById(Lines 9-14).
UserEntityis UUID-keyed, so repository generic must beUUID. CurrentfindById(UUID)only masks the mismatch for one method; inherited methods still useLong.🐛 Proposed fix
import java.util.Optional; import java.util.UUID; @@ -public interface UserRepository extends JpaRepository<UserEntity, Long> { +public interface UserRepository extends JpaRepository<UserEntity, UUID> { @@ - Optional<UserEntity> findById(UUID id); - Optional<UserEntity> findUserEntitiesByUsername(String username);#!/bin/bash set -e # Verify UserEntity `@Id` type rg -n --type=java '@Id|GeneratedValue|UUID|class UserEntity' \ backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java # Verify UserRepository generic and custom findById signature rg -n --type=java 'interface UserRepository|JpaRepository<UserEntity|findById\\(' \ backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javaBased on learnings: In SmartJam,
UserRepositorymust extendJpaRepository<UserEntity, UUID>.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` around lines 9 - 14, The repository declares UserRepository extends JpaRepository<UserEntity, Long> but UserEntity uses UUID keys; update the generic to JpaRepository<UserEntity, UUID> and remove the redundant custom Optional<UserEntity> findById(UUID id) so you rely on the inherited findById(UUID) signature; ensure any necessary import for java.util.UUID is present and adjust/remove any shadowing method references to findById in UserRepository.
11-15: 🧹 Nitpick | 🔵 TrivialRename query signatures for clarity.
findByEmail(String login)mixes terms, andfindUserEntitiesByUsernameis oddly pluralized for a single-entityOptional. PreferfindByEmail(String email)andfindByUsername(String username).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` around lines 11 - 15, Rename the confusing repository method signatures in UserRepository: change findByEmail(String login) to findByEmail(String email) and change findUserEntitiesByUsername(String username) to findByUsername(String username); update all call sites, tests, and any references to those methods to use the new names and parameter names so Spring Data can bind them correctly (ensure method names remain unique and keep return types as Optional<UserEntity>).backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java (1)
8-8: 🧹 Nitpick | 🔵 TrivialReplace
AvailabilityStatuswith auth-domain status.For auth endpoints,
AvailabilityStatusis misleading. Use a dedicated auth status enum or drop this field and rely on HTTP semantics.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java` at line 8, AuthResponse currently exposes AvailabilityStatus which is misleading for auth endpoints; replace it with a domain-specific auth status or remove the field and rely on HTTP status. Create or import an AuthStatus enum (e.g., AUTH_SUCCESS, AUTH_FAILURE, LOCKED, EXPIRED) in the auth domain, change the AuthResponse DTO field type from AvailabilityStatus to AuthStatus (or remove the field entirely), update constructors, getters/setters, any builders, JSON serialization annotations, and all usages of AuthResponse (controllers/services/tests) to use the new enum (or to not set the field) so the DTO and its consumers compile and reflect auth-specific semantics.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java (1)
8-9:⚠️ Potential issue | 🔴 CriticalFix repository ID generic to
UUID(Line 8).
RefreshTokenRepositoryusesLongas ID type, but entity IDs are UUID-based. Inherited methods (findById,deleteById,existsById) will be inconsistent.🐛 Proposed fix
+import java.util.UUID; import java.util.Optional; import com.smartjam.smartjamapi.entity.RefreshTokenEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> { +public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, UUID> { Optional<RefreshTokenEntity> findByToken(String token); }#!/bin/bash set -e # Verify RefreshTokenEntity primary key type rg -n --type=java '@Id|GeneratedValue|UUID|class RefreshTokenEntity' \ backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java # Verify repository generic ID type rg -n --type=java 'interface RefreshTokenRepository|JpaRepository<RefreshTokenEntity' \ backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javaBased on learnings: In SmartJam, RefreshTokenRepository must use
JpaRepository<RefreshTokenEntity, UUID>because the entity primary key is UUID.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java` around lines 8 - 9, The repository generic ID type is wrong: update RefreshTokenRepository so its JpaRepository generic uses UUID instead of Long (i.e., change the second type parameter on JpaRepository for RefreshTokenEntity from Long to UUID) so inherited methods like findById/deleteById/existsById match the RefreshTokenEntity primary key type; ensure imports include java.util.UUID if missing and keep the existing findByToken method signature untouched.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java (1)
16-19:⚠️ Potential issue | 🟠 MajorRemove duplicate
UserDetailsServicebean (Lines 16-19).
CustomUserDetailsServiceis already a@ServiceimplementingUserDetailsService. Keeping this extra bean can create ambiguity for autowiring by type.♻️ Proposed fix
- `@Bean` - public UserDetailsService userDetailsService() { - return customUserDetailsService::loadUserByUsername; - } -#!/bin/bash set -e # Find all UserDetailsService bean definitions and implementations rg -n --type=java 'implements UserDetailsService|@Bean\\s*\\n\\s*public\\s+UserDetailsService|class ApplicationConfig|class CustomUserDetailsService'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java` around lines 16 - 19, Remove the duplicate UserDetailsService bean in ApplicationConfig: delete the userDetailsService() `@Bean` method so Spring will use the existing CustomUserDetailsService `@Service` implementation that already implements UserDetailsService; ensure no other code depends on the removed bean name and rely on autowiring by type to use CustomUserDetailsService (class CustomUserDetailsService, method loadUserByUsername) instead.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java (1)
3-7: 🧹 Nitpick | 🔵 TrivialUse an auth-specific status enum for auth responses.
AvailabilityStatusreads like profile/resource availability, not authentication outcome. ConsiderAuthStatus/AuthenticationStatus(or rely on HTTP status + token presence).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java` around lines 3 - 7, The enum AvailabilityStatus is misnamed for auth responses; rename it to AuthenticationStatus (or AuthStatus) and update all usages (e.g., any controllers, dto classes, and methods that reference AvailabilityStatus) to use the new enum name, ensuring serialization names remain the same if needed (adjust `@JsonProperty/`@JsonValue or mapping logic where present) and run a project-wide refactor to replace AvailabilityStatus with AuthenticationStatus to avoid breaking references.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
32-35:⚠️ Potential issue | 🟠 MajorMap role to authorities (Line 34 currently empty).
Returning an empty collection disables role-based authorization (
hasRole(...),@PreAuthorize, etc.).🔐 Proposed fix
import java.util.Collection; import java.util.List; import java.util.UUID; import com.smartjam.smartjamapi.entity.UserEntity; +import com.smartjam.smartjamapi.enums.Role; import lombok.AllArgsConstructor; import lombok.Getter; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; `@AllArgsConstructor` `@NullMarked` public class UserDetailsImpl implements UserDetails { @@ private String email; private String password; + private Role role; public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash()); + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPasswordHash(), + user.getRole()); } @@ public Collection<? extends GrantedAuthority> getAuthorities() { - return List.of(); + return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); }Based on learnings: In SmartJam,
UserDetailsImpl.getAuthorities()must mapRoletoGrantedAuthority.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 32 - 35, UserDetailsImpl.getAuthorities currently returns an empty list which breaks role-based checks; change it to map the user's Role(s) to GrantedAuthority instances (e.g., for each Role produce a SimpleGrantedAuthority with the "ROLE_" prefix) and return that collection, handling null/absent roles gracefully so methods like hasRole(...) and `@PreAuthorize` work; locate the user's role field or getter in UserDetailsImpl and convert it into a List<GrantedAuthority> instead of List.of().backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd bean-validation constraints to
LoginRequest.
@Validin controller won’t enforce anything until DTO fields are annotated.✅ Proposed fix
package com.smartjam.smartjamapi.dto; -public record LoginRequest(String email, String password) {} +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record LoginRequest( + `@NotBlank` `@Email` String email, + `@NotBlank` `@Size`(min = 8, max = 72) String password) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java` at line 3, The LoginRequest record lacks bean-validation annotations so `@Valid` in controllers does nothing; annotate the record components (the email and password parameters of the LoginRequest record) with appropriate javax.validation.constraints such as `@NotBlank` and `@Email` for email and `@NotBlank` plus a suitable `@Size`(min=...) (or `@Pattern`) for password, and add the necessary imports so the controller’s `@Valid` can enforce validation.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
29-38:⚠️ Potential issue | 🟡 MinorUse proper HTTP status semantics and avoid numeric status literals.
/registershould useHttpStatus.CREATEDinstead of201, and/refreshshould return200 OK(ResponseEntity.ok(...)), not201 Created.Proposed fix
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)); @@ - return ResponseEntity.status(201).body(authService.getNewToken(refreshTokenRequest)); + return ResponseEntity.ok(authService.getNewToken(refreshTokenRequest));Based on learnings: token endpoints like
/refreshshould return 200 OK, and numeric status literals should be replaced with typed constants.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 29 - 38, Replace numeric HTTP status literals with Spring's typed responses and correct the semantics: in the register method (register(...) calling authService.register(...)) return ResponseEntity.status(HttpStatus.CREATED).body(...) or use ResponseEntity.created(...) as appropriate instead of 201, and in getNewToken (getNewToken(...) calling authService.getNewToken(...)) return ResponseEntity.ok(...) (HTTP 200) rather than status 201; update imports if necessary to use HttpStatus/ResponseEntity helpers.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java (1)
31-39: 🧹 Nitpick | 🔵 TrivialCache signing key and reuse
SecureRandominstance.
Both are recreated repeatedly on hot paths.Proposed fix
+import jakarta.annotation.PostConstruct; @@ public class JwtService { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @@ + private Key signingKey; + + `@PostConstruct` + void initSigningKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.signingKey = Keys.hmacShaKeyFor(keyBytes); + } + private Key getSigningKey() { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - return Keys.hmacShaKeyFor(keyBytes); + return signingKey; } @@ - new SecureRandom().nextBytes(randomBytes); + SECURE_RANDOM.nextBytes(randomBytes);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java` around lines 31 - 39, getSigningKey() and generateRefreshToken() allocate expensive objects on each call; cache the decoded signing Key and reuse a single SecureRandom instance instead of recreating them on hot paths. Add a private final (or lazily-initialized thread-safe) field for the Key produced from Decoders.BASE64.decode(secretKey) and return that from getSigningKey(), and add a private final SecureRandom field (initialized once, e.g., in the constructor) to use in generateRefreshToken() when filling randomBytes; ensure thread-safety if you choose lazy initialization.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (2)
25-25:⚠️ Potential issue | 🟡 MinorFix truncated response text.
Return a complete message for/secured/hello.Proposed fix
- return "You are auth"; + return "You are authenticated";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` at line 25, The response string for the /secured/hello endpoint in MainController is truncated; update the return value (the line returning "You are auth") to a complete message such as "You are authenticated" or "You are authorized" so the endpoint returns a full, user-friendly sentence; change the return in the MainController method that handles the "/secured/hello" mapping accordingly.
17-20:⚠️ Potential issue | 🟠 MajorAvoid logging raw principal identifiers at
infolevel.
principal.getName()is PII and is logged redundantly here. Use a single low-verbosity message without the raw identifier.Proposed fix
- log.info(principal.getName()); - - log.info("Call userAccess"); + log.debug("userAccess called"); return principal.getName();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 17 - 20, Remove the info-level logging of the raw principal identifier: delete the log.info(principal.getName()) call in MainController's userAccess method and avoid logging the identifier elsewhere; instead emit a single lower-verbosity message (e.g., log.debug or log.trace) such as "userAccess called" or a redacted token if needed, and keep the existing info-level "Call userAccess" message only if you replace it with non-PII text or lower its level—ensure all references are changed around principal.getName() and the log.info calls in userAccess.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (1)
50-58:⚠️ Potential issue | 🟠 Major401/400 mapping is inverted for current auth exceptions.
IllegalStateExceptionis used for auth failures inAuthService, but here it is mapped to 400. It should be mapped to 401, withIllegalArgumentExceptionmoved to the 400 handler.Proposed fix
- `@ExceptionHandler`(IllegalArgumentException.class) - public ResponseEntity<ErrorResponseDto> handleUnauthenticated(IllegalArgumentException e) { + `@ExceptionHandler`(IllegalStateException.class) + public ResponseEntity<ErrorResponseDto> handleUnauthenticated(IllegalStateException e) { log.warn("Unauthenticated: {}", e.getMessage()); return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); } - `@ExceptionHandler`(exception = {IllegalStateException.class, MethodArgumentNotValidException.class}) + `@ExceptionHandler`(exception = {IllegalArgumentException.class, MethodArgumentNotValidException.class}) public ResponseEntity<ErrorResponseDto> handlerBadRequest(Exception e) {Based on learnings: in SmartJam, authentication failures currently surface as
IllegalStateExceptionand should map to 401 Unauthorized.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 50 - 58, The mappings for auth-related exceptions in GlobalExceptionHandler are inverted: update the `@ExceptionHandler` annotations and handlers so IllegalStateException is handled as 401 Unauthorized and IllegalArgumentException is handled as 400 Bad Request; specifically, modify the method currently named handleUnauthenticated (which logs and returns UNAUTHORIZED) to be annotated for IllegalStateException and keep its log/message but ensure it calls buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, ...), and change the handlerBadRequest method to handle IllegalArgumentException (and other validation exceptions like MethodArgumentNotValidException) returning HttpStatus.BAD_REQUEST with ErrorCode.BAD_REQUEST and appropriate log text. Ensure the annotation parameters and method signatures reflect the swapped exception types.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (6)
60-84:⚠️ Potential issue | 🟠 MajorMake registration atomic and remove check-then-save race.
Line 62 pre-check + Line 72 save is race-prone under concurrent requests, and the method is non-transactional despite multiple writes (Lines 72 and 84). Wrap registration in a transaction and rely on DB uniqueness violation handling for duplicates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 60 - 84, Remove the pre-check using repository.existsByEmail and make the register(RegisterRequest) method transactional so the user creation and RefreshTokenEntity save occur in one DB transaction; instead rely on a unique constraint violation to detect duplicate emails by catching the persistence exception (e.g., DataIntegrityViolationException / ConstraintViolationException) thrown from repository.save(userEntity) and translate it to the existing duplicate-account error, and keep the rest of the flow (building UserDetailsImpl, generating tokens, saving refreshTokenEntity via refreshTokenRepository.save) inside the same `@Transactional` method to avoid race conditions.
50-52:⚠️ Potential issue | 🔴 CriticalPersist hashed refresh tokens, not raw token values.
The code stores and queries refresh tokens in plaintext (
setToken(...),findByToken(...)). This exposes replayable tokens if DB data leaks. Store the hash on write and hash incoming token before lookup.Based on learnings: SmartJam intentionally uses hashed opaque refresh-token storage in
AuthService/RefreshTokenEntity, not raw token persistence.Also applies to: 79-81, 92-93, 106-108
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 50 - 52, The RefreshTokenEntity currently stores raw tokens via refreshTokenEntity.setToken(...) and the AuthService queries via findByToken(...); change this to persist a secure hash instead: when creating or updating a refresh token in AuthService (where setToken is called) hash the token (e.g., SHA-256 or bcrypt/HMAC with a server secret) and store the hash in RefreshTokenEntity, and when validating or looking up a token (places using findByToken(...) and the other occurrences around lines noted) hash the incoming raw token the same way before calling the repository; ensure comparisons use the hashed value and update repository/query methods to expect the hashed token string.
53-53:⚠️ Potential issue | 🟠 MajorUse a dedicated refresh-token TTL.
Refresh-token expiration is derived from access-token expiration (
getJwtExpiration()), which is incorrect for refresh flow. Use a separate configured refresh expiration.Based on learnings: refresh-token lifetime in SmartJam must be configured separately (e.g.,
security.jwt.refresh-expiration-time) and be longer than access-token TTL.Also applies to: 82-82, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 53, The refresh-token TTL is incorrectly using the access-token TTL via jwtService.getJwtExpiration(); update AuthService to use a dedicated refresh expiration configuration (e.g., security.jwt.refresh-expiration-time) exposed by JwtService or a config property method such as jwtService.getRefreshJwtExpiration(), and replace calls like refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())) (and the similar occurrences at the other spots) to use Instant.now().plusMillis(jwtService.getRefreshJwtExpiration()) so refresh tokens have their own, longer lifespan.
36-43:⚠️ Potential issue | 🔴 CriticalUnify login failure responses to prevent account enumeration.
Line 40 and Line 42 return different exception types/messages for “email not found” vs “wrong password”. Use one identical failure path/message for both cases.
Proposed fix
- UserEntity userEntity = repository - .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + UserEntity userEntity = repository + .findByEmail(request.email()) + .orElseThrow(() -> new IllegalStateException("Invalid email or password")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { - throw new IllegalStateException("Invalid password"); + throw new IllegalStateException("Invalid email or password"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 36 - 43, The login flow in AuthService.login currently throws different exceptions/messages for missing email (repository.findByEmail -> NoSuchElementException) vs wrong password (passwordEncoder.matches -> IllegalStateException); change the logic so both failure cases follow a single identical failure path and message (e.g., a single Authentication/IllegalStateException with a constant message) by checking for user presence and password validity and throwing the same exception/message in either case; update AuthService.login to use that unified failure branch and a shared failure message constant to prevent account enumeration.
91-93:⚠️ Potential issue | 🟠 MajorReplace
NoSuchElementExceptionin auth flow with explicitly mapped auth exceptions.Using
NoSuchElementExceptionhere risks falling into generic error handling and returning 500s. Throw application-mapped auth exceptions so missing token/user returns controlled client responses.Also applies to: 98-100
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 91 - 93, The code throws a generic NoSuchElementException when a refresh token or user is missing (see RefreshTokenEntity retrieval via refreshTokenRepository.findByToken(refreshTokenRequest.refreshToken()) and the similar block at lines 98-100); replace those throws with a purpose-built auth exception (e.g., TokenNotFoundException or InvalidRefreshTokenException / AuthenticationException) that your controller/advice maps to a client-safe status (401/400) so the auth flow returns controlled responses instead of 500s; ensure you throw the new exception where the token or associated user is absent and update any exception handler to map it to the appropriate HTTP response.
90-112:⚠️ Potential issue | 🔴 CriticalInvalidate old refresh token during rotation.
getNewToken()issues a new refresh token but leaves the old one valid. Delete/revoke the used token to block refresh-token replay.Proposed fix
if (refreshToken.getExpiresAt().isBefore(Instant.now())) { + refreshTokenRepository.delete(refreshToken); throw new IllegalStateException("Refresh token expired"); } @@ refreshTokenRepository.save(refreshTokenEntity); + refreshTokenRepository.delete(refreshToken);Based on learnings: SmartJam requires invalidating the previous refresh token when issuing a new one in
AuthService.getNewToken().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 90 - 112, getNewToken currently rotates tokens but leaves the old RefreshTokenEntity alive; delete or revoke the used token to prevent replay. After you validate the incoming refreshToken (the local variable refreshToken from refreshTokenRepository.findByToken(...)) and before saving the new RefreshTokenEntity, call the repository to remove the old token (e.g., refreshTokenRepository.delete(refreshToken) or refreshTokenRepository.deleteByToken(...)), then save the new RefreshTokenEntity; ensure you reference getNewToken, refreshTokenRepository, RefreshTokenEntity and jwtService when making the change.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
42-58:⚠️ Potential issue | 🟠 MajorHandle JWT parsing/validation failures inside the filter.
If token extraction/validation throws, this path can produce 500 instead of a controlled unauthorized flow. Catch auth-token parsing/validation exceptions, avoid setting authentication, and continue the chain.
Proposed fix
- final String jwt = authHeader.substring(7); - final String email = jwtService.extractUsername(jwt); - - log.info("Filter for {}", email); - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); - - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - SecurityContextHolder.getContext().setAuthentication(authToken); - } - } + final String jwt = authHeader.substring(7); + try { + final String email = jwtService.extractUsername(jwt); + log.info("Filter for {}", email); + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + } catch (RuntimeException ex) { + log.debug("JWT authentication skipped: {}", ex.getMessage()); + } filterChain.doFilter(request, response);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 42 - 58, The JwtAuthenticationFilter currently calls jwtService.extractUsername(...) and jwtService.isTokenValid(...) without handling exceptions, which can bubble up and return 500; wrap the extraction/validation and user loading logic in a try/catch inside doFilter (around jwtService.extractUsername, customUserDetailsService.loadUserByUsername and jwtService.isTokenValid), on any exception do not set the SecurityContext, log a warning with the exception, and always continue with filterChain.doFilter(request, response) so failures result in an unauthenticated/unauthorized flow instead of a 500.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 45dd43e4-a633-4914-b92b-a918e130a39e
📒 Files selected for processing (28)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.javabackend/smartjam-api/src/main/resources/application.yaml
...end/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
Show resolved
Hide resolved
...nd/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 13
♻️ Duplicate comments (13)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)
11-11: 🧹 Nitpick | 🔵 TrivialRename the parameter to match the query field.
findByEmail(String login)mixes two different terms for the same value and makes auth call sites harder to read. UseString emailhere.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` at line 11, In UserRepository update the method signature for findByEmail to use a parameter name matching the query field by renaming the parameter from login to email (i.e., change findByEmail(String login) to findByEmail(String email)) so call sites and the repository method are consistent with the field name.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (2)
25-28:⚠️ Potential issue | 🔴 CriticalRemove the hardcoded MinIO configuration and credentials.
Bucket/endpoint/access key/secret key are still embedded in source. These values need to come from application properties or environment-backed configuration before this can ship.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 25 - 28, The BUCKET_NAME, S3_ENDPOINT, ACCESS_KEY and SECRET_KEY constants in UploadService are hardcoded; replace them with configurable properties by removing the static constants and injecting values from application properties or environment (e.g. use `@Value`("${s3.bucket}") or a `@ConfigurationProperties` class, or read from org.springframework.core.env.Environment) into instance fields or constructor parameters in UploadService so the values come from application.properties/environment variables (provide sensible property keys like s3.bucket, s3.endpoint, s3.accessKey, s3.secretKey and ensure no credentials remain checked into source).
73-76:⚠️ Potential issue | 🟠 MajorDon’t use the client-supplied
fileNameas the object key.This lets callers choose arbitrary bucket keys/prefixes and overwrite existing objects. Generate the key server-side, or strictly validate an allowed filename format before building the
PutObjectRequest.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 73 - 76, The generateUploadUrl method currently uses the client-supplied fileName directly as the S3 key (PutObjectRequest.builder().key(fileName)), which allows clients to pick arbitrary keys and overwrite objects; change this to generate the object key server-side and/or strictly validate fileName. Replace usage of the raw fileName in PutObjectRequest with a generatedKey (for example: generate a UUID or timestamp-based prefix and append a sanitized file extension from fileName) and ensure you strip/forbid path separators and enforce a strict filename regex (no ../ or slashes, allowed extensions only) to prevent prefix injection; also consider making the key unique to avoid accidental overwrites and return that generatedKey in the UploadUrlResponse instead of the original fileName. Ensure changes are applied in generateUploadUrl and where PutObjectRequest, BUCKET_NAME, and UploadUrlResponse are used.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java (1)
61-69: 🧹 Nitpick | 🔵 TrivialDelete the commented-out
UserDetailsmethods.This logic is no longer active and just leaves stale security behavior hints inside the JPA entity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java` around lines 61 - 69, Remove the stale commented-out UserDetails implementations from the UserEntity class: delete the commented getAuthorities() and getPassword() blocks (the lines referencing SimpleGrantedAuthority, ROLE_ + role.name(), and passwordHash) so the entity no longer contains inactive/security-related commented code; ensure no other references to these commented methods remain in UserEntity.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)
3-3:⚠️ Potential issue | 🟠 MajorAdd Bean Validation annotations to the record components.
@Validon the controller does nothing untilnickname, andpassworddeclare constraints here, so blank or malformed registration data still reachesAuthService.register(...). At minimum, this DTO should carry@NotBlankand an explicit password size policy.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java` at line 3, The RegisterRequest record components lack Bean Validation constraints, so annotate the components in the RegisterRequest record (email, nickname, password) with appropriate javax/jakarta.validation annotations: add `@Email` and `@NotBlank` to email, `@NotBlank` to nickname, and `@NotBlank` plus an explicit `@Size`(min=8, max=128) (or your chosen policy) to password so controller `@Valid` actually enforces input rules before calling AuthService.register(...); ensure imports for the validation annotations are added and adjust sizes to match your password policy.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (2)
25-27:⚠️ Potential issue | 🟡 MinorTypo: "auth" should be "authenticated".
The message "You are auth" appears truncated. This was flagged in a previous review and remains unaddressed.
Proposed fix
public String hello() { - return "You are auth"; + return "You are authenticated"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 25 - 27, The hello() method in MainController currently returns the truncated message "You are auth"; update the return value of the hello() method to the full word "You are authenticated" so the endpoint response is clear and correct.
15-20: 🧹 Nitpick | 🔵 TrivialRemove redundant logging of principal name.
The principal's name is logged twice: once directly (line 17) and again in a formatted message (line 19). Consolidate to a single meaningful log statement.
Proposed fix
`@GetMapping`("/user") public String userAccess(Principal principal) { - log.info(principal.getName()); - log.info("userAccess called for: {}", principal.getName()); return principal.getName(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 15 - 20, The method MainController.userAccess logs the principal name twice (direct log.info(principal.getName()) and log.info("userAccess called for: {}", principal.getName())); remove the redundant direct call and keep a single meaningful log statement (e.g., the formatted "userAccess called for: {}") so only one log.info invocation remains that uses the formatted message and principal parameter.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java (1)
1-10:⚠️ Potential issue | 🟡 MinorFix Spotless formatting violations.
The pipeline reports
spotlessJavaCheckfailed due to format violations (import order/spacing). Run./gradlew spotlessApplybefore committing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java` around lines 1 - 10, Spotless formatting violations are caused by import ordering/spacing in CustomUserDetailsService; run ./gradlew spotlessApply to auto-fix formatting, then verify the imports and spacing around the class use the project's style (remove any unused imports and ensure imports like UserEntity, UserRepository, UserDetailsImpl, NotBlank, RequiredArgsConstructor, UserDetailsService, UsernameNotFoundException, Service are ordered and spaced according to the formatter). After applying spotless, re-run the build to confirm spotlessJavaCheck passes and commit the reformatted file.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
29-32: 🧹 Nitpick | 🔵 TrivialUse
HttpStatus.CREATEDinstead of the numeric literal here.
201is the right status for registration, but hard-coding it makes the controller harder to scan and easy to regress again.Based on learnings: In Java Spring controllers, use proper HTTP status semantics and avoid hard-coded numeric status codes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` around lines 29 - 32, Replace the hard-coded numeric HTTP status in AuthController.register with the Spring enum constant: use ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)) so the method RegisterRequest -> AuthResponse flow remains the same but uses HttpStatus.CREATED instead of 201; import org.springframework.http.HttpStatus if missing and update the register method accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (2)
45-50:⚠️ Potential issue | 🟠 MajorReturn one generic login failure for both branches.
NoSuchElementException("Login not found...")andIllegalStateException("Invalid password")still make the login path distinguishable, which lets callers enumerate registered emails.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 45 - 50, The current logic in AuthService uses repository.findByEmail(...) which throws NoSuchElementException for missing emails and then throws IllegalStateException for password mismatch, leaking which branch failed; change the method so both branches return the same generic authentication failure: after calling repository.findByEmail(request.email()) handle the absent user and the password check failure by throwing a single generic exception (e.g., AuthenticationException or a custom InvalidCredentialsException) with the same message; ensure the references to UserEntity, passwordEncoder.matches(request.password(), userEntity.getPasswordHash()), and repository.findByEmail(request.email()) are updated to use that single exception path so callers cannot distinguish “not found” vs “bad password.”
69-80:⚠️ Potential issue | 🟠 Major
existsByEmail()is still a non-atomic pre-check.Two concurrent registrations can both pass Line 71 and one will fail later at insert time. The uniqueness decision needs to happen at the write, with the persistence error translated back into the same business response.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 69 - 80, The pre-check using repository.existsByEmail(...) in register(...) is non-atomic and can race; keep or remove the pre-check but ensure repository.save(userEntity) is wrapped to catch persistence-level unique constraint violations (e.g., DataIntegrityViolationException / ConstraintViolationException / PersistenceException) and translate them into the same business error (throw new IllegalStateException("The account exists, try login, please")). Update AuthService.register to catch those DB exceptions around repository.save(userEntity) and rethrow the consistent IllegalStateException so concurrent inserts yield the same response.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java (1)
42-58:⚠️ Potential issue | 🟠 MajorHandle token parsing and user lookup failures inside the filter.
jwtService.extractUsername(jwt)andcustomUserDetailsService.loadUserByUsername(email)can both throw before the request ever reaches@RestControllerAdvice, so malformed or expired tokens still bubble out as 500s here instead of leaving the request unauthenticated.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java` around lines 42 - 58, In JwtAuthenticationFilter, wrap calls to jwtService.extractUsername(jwt) and customUserDetailsService.loadUserByUsername(email) in a try/catch that handles parsing/lookup exceptions (e.g., malformed/expired token or user lookup errors), log them at debug/trace level, and ensure you do not set an Authentication on SecurityContext when an exception occurs; simply continue to filterChain.doFilter(request, response) so the request proceeds unauthenticated. Include checks to skip authentication on null/invalid email or if any exception was caught before calling jwtService.isTokenValid and creating the UsernamePasswordAuthenticationToken.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java (1)
28-35:⚠️ Potential issue | 🟠 MajorMap the user's role into
getAuthorities()instead of returningList.of().With an empty authority list here, access tokens are minted without roles and any role-based checks will always fail.
Based on learnings: In SmartJam, UserDetailsImpl.getAuthorities() should map the stored Role enum to a GrantedAuthority instead of returning an empty list.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java` around lines 28 - 35, UserDetailsImpl.getAuthorities() must return the user's role as a GrantedAuthority instead of an empty list: add a role field to UserDetailsImpl, populate it in the static build(UserEntity user) using user.getRole() (or appropriate getter on UserEntity), and implement getAuthorities() to return a collection with a SimpleGrantedAuthority built from that role (e.g. prefix with "ROLE_" + role.name() or use role string value). Ensure imports for org.springframework.security.core.authority.SimpleGrantedAuthority and that other constructors/getters are updated to include the role field so the authority mapping uses the stored role.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 18-22: The `@NotBlank` on the loadUserByUsername(String email)
parameter won't be enforced unless method validation is enabled; update
CustomUserDetailsService by either (A) enabling Spring method validation
(register a MethodValidationPostProcessor bean and annotate the class with
`@Validated`) so `@NotBlank` is enforced on UserDetailsService.loadUserByUsername,
or (B) add an explicit defensive check at the start of loadUserByUsername
(validate email != null && !email.isBlank()) and throw a clear
UsernameNotFoundException or IllegalArgumentException before calling
userRepository.findByEmail(...); choose one approach and apply it consistently.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Line 36: The method signature for getNewTokens has extra whitespace and fails
the Spotless formatter; run the project's Spotless/formatter (or apply the Java
formatter) to reformat this method signature in AuthController#getNewTokens and
remove the stray spaces around the parameters so the signature exactly matches
project style (e.g., ensure only single spaces and proper annotations on
RefreshTokenRequest), then re-run CI to verify formatting passes.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Line 21: Spotless formatting violations are present around the `@PreAuthorize`
annotation in MainController; run ./gradlew spotlessApply to auto-fix
formatting, and if any comment indentation still fails, open MainController.java
and align the comment block immediately above the `@PreAuthorize` annotation to
match project style (same indentation level as the annotation) so spotlessCheck
passes for the `@PreAuthorize` usage.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java`:
- Around line 1-9: Spotless formatting violations in RefreshTokenRequest:
reorder imports (place jakarta.validation.constraints.NotBlank and
com.fasterxml.jackson.annotation.JsonProperty in the project’s configured import
order) and fix annotation/parameter formatting on the record component (make the
annotations and the String refreshToken parameter match the project's
style—e.g., single-line component with annotations inline if required). Update
the RefreshTokenRequest record (references: RefreshTokenRequest, `@NotBlank`,
`@JsonProperty`) accordingly and run ./gradlew spotlessApply to auto-apply
formatting before committing.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java`:
- Line 1: The file RefreshTokenEntity has Spotless formatting violations; run
the formatter and commit the fixes by executing ./gradlew spotlessApply, then
re-run the build to confirm spotlessJavaCheck passes; specifically reformat the
RefreshTokenEntity class (package declaration, imports, spacing, indentation,
and any trailing whitespace) so it conforms to the project's Spotless rules and
include the resulting changed file in your commit.
- Around line 14-15: The RefreshTokenEntity currently uses Lombok's `@Data` which
generates toString/equals/hashCode that can touch the lazy 'user' association;
replace `@Data` on RefreshTokenEntity with `@Getter` and `@Setter` (and keep
`@NoArgsConstructor`), and implement equals(Object) and hashCode() manually to use
only the primary identifier (id) or otherwise exclude the 'user' field from
those methods; ensure to remove any Lombok-generated toString that includes
'user' (or annotate user to be excluded) so lazy loading won't be triggered
outside a transaction.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java`:
- Around line 3-9: The ErrorCode enum defines both NOT_FOUND and
RESOURCE_NOT_FOUND which are semantically overlapping; pick a single
representative constant (e.g., NOT_FOUND) and remove the duplicate
(RESOURCE_NOT_FOUND) from ErrorCode, then update all usages to reference the
chosen constant (search for ErrorCode.RESOURCE_NOT_FOUND and
ErrorCode.NOT_FOUND) or add a deprecated alias for RESOURCE_NOT_FOUND that maps
to NOT_FOUND to preserve backward compatibility before removing; ensure tests
and any switch/case handling in controllers/services (where ErrorCode is
matched) are updated accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 67-72: The `@ExceptionHandler` on
GlobalExceptionHandler.handleAuthException lists AuthenticationException and
IllegalStateException but the method parameter is typed as
AuthenticationException, causing Spring to fail binding IllegalStateException;
update handleAuthException to accept a common supertype (e.g., Exception or
RuntimeException) or split into two handlers (one for AuthenticationException,
one for IllegalStateException) so the parameter type is assignable from all
declared exceptions and adjust the logging/response building accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java`:
- Around line 5-8: Rename the generic SecurityException class to a
domain-specific name (e.g., AuthenticationFailureException or
InvalidAuthenticationException) to avoid collision with
java.lang.SecurityException and clarify intent; update the class declaration
(currently "public class SecurityException extends AuthenticationException") to
the new name, then update all throw sites, imports and exception handler
mappings that reference SecurityException to use the new type, and optionally
keep a short deprecated SecurityException wrapper (extending the new type) if
backward compatibility is required.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java`:
- Around line 6-8: The UserMapper.toEntity mapping currently leaves fields like
passwordHash, role, createdAt, updatedAt (and any other fields MapStruct warns
as unmapped) implicit; update the mapper contract by adding explicit `@Mapping`
annotations on the toEntity method to ignore those fields (e.g., `@Mapping`(target
= "passwordHash", ignore = true), `@Mapping`(target = "role", ignore = true),
`@Mapping`(target = "createdAt", ignore = true), `@Mapping`(target = "updatedAt",
ignore = true), etc.) so MapStruct warnings are silenced and it’s clear which
fields are intentionally filled elsewhere (AuthService.register).
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 117-141: Replace the current read-then-update flow in
AuthService.refresh handling with an atomic DB conditional update that only
transitions an ACTIVE refresh token to USED and returns the affected row count
(use or add a repository method like
refreshTokenRepository.setStatusIfActive(tokenHash, StatusRefreshToken.ACTIVE,
StatusRefreshToken.USED) or similar); after calling that atomic update, reject
the request unless exactly one row was claimed (throw
SecurityException/TokenExpiredException as appropriate), and treat any
non-ACTIVE status (USED, REVOKED, etc.) as rejection without proceeding to
generate new tokens; keep existing revokeToken and logging behavior when
rejecting.
- Around line 7-10: AuthService is throwing new SecurityException("Token reuse
detected") but the file lacks the project exception import, causing Java to
resolve java.lang.SecurityException and bypass the AuthenticationException
handler; add the import for the project exception
(com.smartjam.smartjamapi.exception.SecurityException) at the top of AuthService
so the thrown SecurityException resolves to the project type and is handled by
the AuthenticationException handler.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Around line 59-70: The bucket initialization currently swallows failures (in
the UploadService block using s3Client.listBuckets() / s3Client.createBucket()),
allowing startup to continue with an invalid storage state; change this to fail
fast by rethrowing a runtime exception after logging (e.g., throw new
IllegalStateException("Failed to initialize bucket", e)) or set a clear
storage-unavailable flag and prevent URL issuance from getSignedUrl/createUpload
methods; update the catch in UploadService to either rethrow the caught
Exception or set a boolean like storageAvailable=false and ensure other methods
check that flag before issuing URLs.
---
Duplicate comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`:
- Around line 1-10: Spotless formatting violations are caused by import
ordering/spacing in CustomUserDetailsService; run ./gradlew spotlessApply to
auto-fix formatting, then verify the imports and spacing around the class use
the project's style (remove any unused imports and ensure imports like
UserEntity, UserRepository, UserDetailsImpl, NotBlank, RequiredArgsConstructor,
UserDetailsService, UsernameNotFoundException, Service are ordered and spaced
according to the formatter). After applying spotless, re-run the build to
confirm spotlessJavaCheck passes and commit the reformatted file.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Around line 29-32: Replace the hard-coded numeric HTTP status in
AuthController.register with the Spring enum constant: use
ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)) so
the method RegisterRequest -> AuthResponse flow remains the same but uses
HttpStatus.CREATED instead of 201; import org.springframework.http.HttpStatus if
missing and update the register method accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Around line 25-27: The hello() method in MainController currently returns the
truncated message "You are auth"; update the return value of the hello() method
to the full word "You are authenticated" so the endpoint response is clear and
correct.
- Around line 15-20: The method MainController.userAccess logs the principal
name twice (direct log.info(principal.getName()) and log.info("userAccess called
for: {}", principal.getName())); remove the redundant direct call and keep a
single meaningful log statement (e.g., the formatted "userAccess called for:
{}") so only one log.info invocation remains that uses the formatted message and
principal parameter.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java`:
- Line 3: The RegisterRequest record components lack Bean Validation
constraints, so annotate the components in the RegisterRequest record (email,
nickname, password) with appropriate javax/jakarta.validation annotations: add
`@Email` and `@NotBlank` to email, `@NotBlank` to nickname, and `@NotBlank` plus an
explicit `@Size`(min=8, max=128) (or your chosen policy) to password so controller
`@Valid` actually enforces input rules before calling AuthService.register(...);
ensure imports for the validation annotations are added and adjust sizes to
match your password policy.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java`:
- Around line 61-69: Remove the stale commented-out UserDetails implementations
from the UserEntity class: delete the commented getAuthorities() and
getPassword() blocks (the lines referencing SimpleGrantedAuthority, ROLE_ +
role.name(), and passwordHash) so the entity no longer contains
inactive/security-related commented code; ensure no other references to these
commented methods remain in UserEntity.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Line 11: In UserRepository update the method signature for findByEmail to use
a parameter name matching the query field by renaming the parameter from login
to email (i.e., change findByEmail(String login) to findByEmail(String email))
so call sites and the repository method are consistent with the field name.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java`:
- Around line 42-58: In JwtAuthenticationFilter, wrap calls to
jwtService.extractUsername(jwt) and
customUserDetailsService.loadUserByUsername(email) in a try/catch that handles
parsing/lookup exceptions (e.g., malformed/expired token or user lookup errors),
log them at debug/trace level, and ensure you do not set an Authentication on
SecurityContext when an exception occurs; simply continue to
filterChain.doFilter(request, response) so the request proceeds unauthenticated.
Include checks to skip authentication on null/invalid email or if any exception
was caught before calling jwtService.isTokenValid and creating the
UsernamePasswordAuthenticationToken.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java`:
- Around line 28-35: UserDetailsImpl.getAuthorities() must return the user's
role as a GrantedAuthority instead of an empty list: add a role field to
UserDetailsImpl, populate it in the static build(UserEntity user) using
user.getRole() (or appropriate getter on UserEntity), and implement
getAuthorities() to return a collection with a SimpleGrantedAuthority built from
that role (e.g. prefix with "ROLE_" + role.name() or use role string value).
Ensure imports for
org.springframework.security.core.authority.SimpleGrantedAuthority and that
other constructors/getters are updated to include the role field so the
authority mapping uses the stored role.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Around line 45-50: The current logic in AuthService uses
repository.findByEmail(...) which throws NoSuchElementException for missing
emails and then throws IllegalStateException for password mismatch, leaking
which branch failed; change the method so both branches return the same generic
authentication failure: after calling repository.findByEmail(request.email())
handle the absent user and the password check failure by throwing a single
generic exception (e.g., AuthenticationException or a custom
InvalidCredentialsException) with the same message; ensure the references to
UserEntity, passwordEncoder.matches(request.password(),
userEntity.getPasswordHash()), and repository.findByEmail(request.email()) are
updated to use that single exception path so callers cannot distinguish “not
found” vs “bad password.”
- Around line 69-80: The pre-check using repository.existsByEmail(...) in
register(...) is non-atomic and can race; keep or remove the pre-check but
ensure repository.save(userEntity) is wrapped to catch persistence-level unique
constraint violations (e.g., DataIntegrityViolationException /
ConstraintViolationException / PersistenceException) and translate them into the
same business error (throw new IllegalStateException("The account exists, try
login, please")). Update AuthService.register to catch those DB exceptions
around repository.save(userEntity) and rethrow the consistent
IllegalStateException so concurrent inserts yield the same response.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Around line 25-28: The BUCKET_NAME, S3_ENDPOINT, ACCESS_KEY and SECRET_KEY
constants in UploadService are hardcoded; replace them with configurable
properties by removing the static constants and injecting values from
application properties or environment (e.g. use `@Value`("${s3.bucket}") or a
`@ConfigurationProperties` class, or read from
org.springframework.core.env.Environment) into instance fields or constructor
parameters in UploadService so the values come from
application.properties/environment variables (provide sensible property keys
like s3.bucket, s3.endpoint, s3.accessKey, s3.secretKey and ensure no
credentials remain checked into source).
- Around line 73-76: The generateUploadUrl method currently uses the
client-supplied fileName directly as the S3 key
(PutObjectRequest.builder().key(fileName)), which allows clients to pick
arbitrary keys and overwrite objects; change this to generate the object key
server-side and/or strictly validate fileName. Replace usage of the raw fileName
in PutObjectRequest with a generatedKey (for example: generate a UUID or
timestamp-based prefix and append a sanitized file extension from fileName) and
ensure you strip/forbid path separators and enforce a strict filename regex (no
../ or slashes, allowed extensions only) to prevent prefix injection; also
consider making the key unique to avoid accidental overwrites and return that
generatedKey in the UploadUrlResponse instead of the original fileName. Ensure
changes are applied in generateUploadUrl and where PutObjectRequest,
BUCKET_NAME, and UploadUrlResponse are used.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4b3b76cd-eb7f-441d-a1a9-f4980aba9356
📒 Files selected for processing (25)
backend/smartjam-api/build.gradlebackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.javabackend/smartjam-api/src/main/resources/application.yaml
| @Override | ||
| public UserDetailsImpl loadUserByUsername(@NotBlank String email) throws UsernameNotFoundException { | ||
| UserEntity user = userRepository | ||
| .findByEmail(email) | ||
| .orElseThrow(() -> new UsernameNotFoundException("Invalid credentials")); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
@NotBlank on method parameter requires Spring method validation to be enabled.
The @NotBlank annotation on email won't enforce validation at runtime unless MethodValidationPostProcessor is configured and the class is annotated with @Validated. Without this, null/blank values will still reach findByEmail. Consider adding an explicit null check for defense-in-depth.
Proposed defensive check
`@Override`
public UserDetailsImpl loadUserByUsername(`@NotBlank` String email) throws UsernameNotFoundException {
+ if (email == null || email.isBlank()) {
+ throw new UsernameNotFoundException("Invalid credentials");
+ }
UserEntity user = userRepository
.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Invalid credentials"));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java`
around lines 18 - 22, The `@NotBlank` on the loadUserByUsername(String email)
parameter won't be enforced unless method validation is enabled; update
CustomUserDetailsService by either (A) enabling Spring method validation
(register a MethodValidationPostProcessor bean and annotate the class with
`@Validated`) so `@NotBlank` is enforced on UserDetailsService.loadUserByUsername,
or (B) add an explicit defensive check at the start of loadUserByUsername
(validate email != null && !email.isBlank()) and throw a clear
UsernameNotFoundException or IllegalArgumentException before calling
userRepository.findByEmail(...); choose one approach and apply it consistently.
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
Outdated
Show resolved
Hide resolved
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
Show resolved
Hide resolved
| public class SecurityException extends AuthenticationException { | ||
| public SecurityException(String message) { | ||
| super(message); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Use a domain-specific exception name here.
SecurityException collides with java.lang.SecurityException and is too generic for auth code. A name tied to the real failure mode makes throw sites, catches, and handler mappings much clearer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java`
around lines 5 - 8, Rename the generic SecurityException class to a
domain-specific name (e.g., AuthenticationFailureException or
InvalidAuthenticationException) to avoid collision with
java.lang.SecurityException and clarify intent; update the class declaration
(currently "public class SecurityException extends AuthenticationException") to
the new name, then update all throw sites, imports and exception handler
mappings that reference SecurityException to use the new type, and optionally
keep a short deprecated SecurityException wrapper (extending the new type) if
backward compatibility is required.
| @Mapper(componentModel = "spring") | ||
| public interface UserMapper { | ||
| UserEntity toEntity(RegisterRequest request); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Make the intentionally unmapped fields explicit in the mapper contract.
MapStruct is already warning that passwordHash, role, timestamps, and other fields are unmapped. Since AuthService.register(...) fills some of those later, add explicit ignore = true mappings here so future omissions do not get buried in an existing warning.
🧰 Tools
🪛 GitHub Actions: Backend CI
[warning] 8-8: MapStruct warning: Unmapped target properties: "id, passwordHash, firstName, lastName, avatarUrl, role, fcmToken, createdAt, updatedAt". In 'UserEntity toEntity(RegisterRequest request)'.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java`
around lines 6 - 8, The UserMapper.toEntity mapping currently leaves fields like
passwordHash, role, createdAt, updatedAt (and any other fields MapStruct warns
as unmapped) implicit; update the mapper contract by adding explicit `@Mapping`
annotations on the toEntity method to ignore those fields (e.g., `@Mapping`(target
= "passwordHash", ignore = true), `@Mapping`(target = "role", ignore = true),
`@Mapping`(target = "createdAt", ignore = true), `@Mapping`(target = "updatedAt",
ignore = true), etc.) so MapStruct warnings are silenced and it’s clear which
fields are intentionally filled elsewhere (AuthService.register).
| import com.smartjam.smartjamapi.exception.TokenExpiredException; | ||
| import com.smartjam.smartjamapi.exception.TokenNotFoundException; | ||
| import com.smartjam.smartjamapi.mapper.UserMapper; | ||
| import com.smartjam.smartjamapi.security.RefreshTokenService; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Throw site in AuthService ==="
rg -n -C2 'throw new SecurityException' backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
echo
echo "=== AuthService imports ==="
sed -n '1,25p' backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
echo
echo "=== Project SecurityException declaration ==="
sed -n '1,25p' backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.javaRepository: Satlykovs/SmartJam
Length of output: 1756
Add missing import for SecurityException at line 121.
AuthService throws new SecurityException("Token reuse detected") but has no import for the project exception type. This causes Java to resolve SecurityException to java.lang.SecurityException instead of com.smartjam.smartjamapi.exception.SecurityException. Since java.lang.SecurityException does not extend AuthenticationException, the exception bypasses the AuthenticationException handler and falls back to a generic 500 error response.
Add the import:
import com.smartjam.smartjamapi.exception.SecurityException;Also applies to: 121-121
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`
around lines 7 - 10, AuthService is throwing new SecurityException("Token reuse
detected") but the file lacks the project exception import, causing Java to
resolve java.lang.SecurityException and bypass the AuthenticationException
handler; add the import for the project exception
(com.smartjam.smartjamapi.exception.SecurityException) at the top of AuthService
so the thrown SecurityException resolves to the project type and is handled by
the AuthenticationException handler.
| if (refreshToken.getStatus() == StatusRefreshToken.USED) { | ||
| revokeToken(tokenHash); | ||
| log.error(tokenHash); | ||
|
|
||
| throw new SecurityException("Token reuse detected"); | ||
| } | ||
|
|
||
| if (refreshToken.getExpiresAt().isBefore(Instant.now())) { | ||
| revokeToken(tokenHash); | ||
| log.error(tokenHash); | ||
|
|
||
| throw new TokenExpiredException("Refresh token expired"); | ||
| } | ||
|
|
||
| refreshTokenRepository.setStatusByRefreshToken(tokenHash, StatusRefreshToken.USED); | ||
| refreshTokenRepository.flush(); | ||
|
|
||
| UserEntity userEntity = refreshToken.getUser(); | ||
| UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); | ||
|
|
||
| String accessToken = jwtService.generateAccessToken(userDetails); | ||
| String newRefreshToken = refreshTokenService.generateRefreshToken(); | ||
|
|
||
| RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, newRefreshToken); | ||
| refreshTokenRepository.save(refreshTokenEntity); |
There was a problem hiding this comment.
Reject every non-ACTIVE refresh token, and do it atomically.
Right now only USED is blocked. A token already marked REVOKED falls through to Line 131 and gets a fresh token pair, and the separate read/check/update also lets two concurrent refreshes race. Gate rotation on a single ACTIVE -> USED update (or a lock) and continue only when exactly one row was claimed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`
around lines 117 - 141, Replace the current read-then-update flow in
AuthService.refresh handling with an atomic DB conditional update that only
transitions an ACTIVE refresh token to USED and returns the affected row count
(use or add a repository method like
refreshTokenRepository.setStatusIfActive(tokenHash, StatusRefreshToken.ACTIVE,
StatusRefreshToken.USED) or similar); after calling that atomic update, reject
the request unless exactly one row was claimed (throw
SecurityException/TokenExpiredException as appropriate), and treat any
non-ACTIVE status (USED, REVOKED, etc.) as rejection without proceeding to
generate new tokens; keep existing revokeToken and logging behavior when
rejecting.
| try { | ||
| log.info("Trying to connect to MinIO at {}", S3_ENDPOINT); | ||
| if (s3Client.listBuckets().buckets().stream() | ||
| .noneMatch(b -> b.name().equals(BUCKET_NAME))) { | ||
| s3Client.createBucket(b -> b.bucket(BUCKET_NAME)); | ||
| log.info("Bucket '{}' created successfully", BUCKET_NAME); | ||
| } else { | ||
| log.info("Bucket '{}' already exists", BUCKET_NAME); | ||
| } | ||
| } catch (Exception e) { | ||
| log.error("Failed to initialize bucket: {}", e.getMessage(), e); | ||
| } |
There was a problem hiding this comment.
Don’t continue startup after bucket initialization fails.
If listBuckets() or createBucket() fails, the service only logs the error and the upload API keeps issuing URLs for a bucket that may not exist. Fail fast here, or explicitly mark the storage path unavailable, instead of silently starting in a degraded state.
Suggested change
} catch (Exception e) {
- log.error("Failed to initialize bucket: {}", e.getMessage(), e);
+ throw new IllegalStateException("Failed to initialize S3/MinIO bucket", e);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`
around lines 59 - 70, The bucket initialization currently swallows failures (in
the UploadService block using s3Client.listBuckets() / s3Client.createBucket()),
allowing startup to continue with an invalid storage state; change this to fail
fast by rethrowing a runtime exception after logging (e.g., throw new
IllegalStateException("Failed to initialize bucket", e)) or set a clear
storage-unavailable flag and prevent URL issuance from getSignedUrl/createUpload
methods; update the catch in UploadService to either rethrow the caught
Exception or set a boolean like storageAvailable=false and ensure other methods
check that flag before issuing URLs.
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (10)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (2)
26-26:⚠️ Potential issue | 🟡 MinorFix truncated user-facing response text.
Line 26 returns
"You are auth", which reads incomplete.✏️ Proposed fix
- return "You are auth"; + return "You are authenticated";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` at line 26, The user-facing response in MainController's endpoint returns the truncated string "You are auth"; update the return value in the method inside class MainController to a complete, user-friendly message such as "You are authenticated." (or "You are authorized." if appropriate for the endpoint's semantics) by replacing the truncated string in that return statement to the full phrase.
17-20:⚠️ Potential issue | 🟠 MajorDo not log raw principal identifiers at INFO level.
Line 17 and Line 19 log
principal.getName()directly atinfo, which leaks user identifiers into standard logs. Keep this at debug and avoid raw identifiers.♻️ Proposed fix
- log.info(principal.getName()); - - log.info("userAccess called for: {}", principal.getName()); + log.debug("userAccess called"); return principal.getName();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java` around lines 17 - 20, In MainController (method handling userAccess) stop logging raw principal identifiers at INFO: change the two log.info(...) calls that emit principal.getName() to a lower level (debug) and avoid writing the raw identifier; instead log a masked or hashed form of principal.getName() (or drop the value entirely) so only non-sensitive context is emitted. Update the logging calls around principal.getName() to use log.debug and replace the raw value with a mask/hash or remove it, keeping the return of principal.getName() unchanged if needed by callers.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (1)
30-31: 🛠️ Refactor suggestion | 🟠 MajorStatic fields create lifecycle and testability issues.
Using
staticfields forS3ClientandS3Presignercircumvents Spring's lifecycle management:
- Cannot be mocked/injected in unit tests without reflection or PowerMock
- Survives Spring context restarts (e.g., in tests with
@DirtiesContext)- Resources are never released on application shutdown (no
@PreDestroyhook to call.close())Declare these as instance fields and configure them as proper
@Beans in a@Configurationclass, injecting them into this service.♻️ Suggested approach
Create a configuration class:
`@Configuration` public class S3Config { `@Value`("${s3.endpoint}") private String endpoint; `@Value`("${s3.access-key}") private String accessKey; `@Value`("${s3.secret-key}") private String secretKey; `@Value`("${s3.region}") private String region; `@Bean` public S3Client s3Client() { return S3Client.builder() .endpointOverride(URI.create(endpoint)) .region(Region.of(region)) .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) .credentialsProvider(StaticCredentialsProvider.create( AwsBasicCredentials.create(accessKey, secretKey))) .build(); } `@Bean` public S3Presigner s3Presigner() { return S3Presigner.builder() .endpointOverride(URI.create(endpoint)) .region(Region.of(region)) .serviceConfiguration(S3Configuration.builder() .pathStyleAccessEnabled(true).build()) .credentialsProvider(StaticCredentialsProvider.create( AwsBasicCredentials.create(accessKey, secretKey))) .build(); } }Then inject into
UploadService:`@Service` `@Slf4j` +@RequiredArgsConstructor public class UploadService { - private static S3Presigner presigner; - private static S3Client s3Client; + private final S3Presigner presigner; + private final S3Client s3Client;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java` around lines 30 - 31, UploadService currently holds static S3Client and S3Presigner fields which break Spring lifecycle and testability; change them to instance fields and remove static usage, create a `@Configuration` class (e.g., S3Config) that defines S3Client and S3Presigner `@Bean` methods using configured endpoint/region/credentials, inject those beans into UploadService via constructor injection, and add a `@PreDestroy` method or rely on Spring-managed bean lifecycle to close resources so clients are cleanly shut down.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)
11-11: 🧹 Nitpick | 🔵 TrivialRename parameter
logintoThe method is
findByEmailbut the parameter is namedlogin. This creates confusion at call sites.♻️ Proposed fix
- Optional<UserEntity> findByEmail(String login); + Optional<UserEntity> findByEmail(String email);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java` at line 11, In UserRepository, rename the method parameter in findByEmail from "login" to "email" to match the method name and avoid confusion at call sites; update the method signature in the UserRepository interface (findByEmail) so the parameter is named email and ensure any implementing classes or usages that reference the parameter name (e.g., in method references or tests) are adjusted accordingly.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (1)
32-32:⚠️ Potential issue | 🟡 MinorReplace magic status code with
HttpStatus.CREATED.Use typed status constants instead of numeric literals for readability and consistency.
✏️ Suggested fix
+import org.springframework.http.HttpStatus; @@ - return ResponseEntity.status(201).body(authService.register(request)); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));Based on learnings: In Java Spring controllers, numeric status literals like
ResponseEntity.status(201)should be replaced with typed constants such asHttpStatus.CREATED.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java` at line 32, Replace the numeric HTTP status literal in AuthController's register response with the typed constant: change the ResponseEntity.status(201) call in AuthController (method that returns register response) to use HttpStatus.CREATED so the controller returns ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request)) for clarity and consistency.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java (4)
121-121:⚠️ Potential issue | 🔴 Critical
SecurityExceptionhere likely resolves to the JDK type, not your auth exception.Without importing
com.smartjam.smartjamapi.exception.SecurityException, Line 121 resolves tojava.lang.SecurityException, which won’t be handled as your auth exception path.🔧 Suggested fix
+import com.smartjam.smartjamapi.exception.SecurityException;#!/bin/bash set -euo pipefail echo "=== Check throw site and imports in AuthService ===" fd 'AuthService.java$' -t f --exec sed -n '1,40p' {} fd 'AuthService.java$' -t f --exec rg -n 'throw new SecurityException' {} echo echo "=== Inspect project SecurityException type ===" fd 'SecurityException.java$' -t f --exec sed -n '1,80p' {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 121, The throw in AuthService (the statement "throw new SecurityException(\"Token reuse detected\")") is resolving to java.lang.SecurityException; change it to throw your app's exception by importing com.smartjam.smartjamapi.exception.SecurityException at the top of AuthService or by using the fully-qualified name com.smartjam.smartjamapi.exception.SecurityException in the throw expression so the service triggers your project's auth-exception handling path.
44-49:⚠️ Potential issue | 🔴 CriticalUnify login failure path to prevent account enumeration.
Line 46 and Line 48 expose different failure reasons (
user missingvswrong password). Return the same exception/message for both branches.🔒 Suggested fix
- UserEntity userEntity = repository - .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + UserEntity userEntity = repository + .findByEmail(request.email()) + .orElseThrow(() -> new IllegalStateException("Invalid credentials")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { - throw new IllegalStateException("Invalid password"); + throw new IllegalStateException("Invalid credentials"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 44 - 49, AuthService currently throws different exceptions for missing users (repository.findByEmail -> NoSuchElementException) and bad passwords (passwordEncoder.matches -> IllegalStateException), enabling account enumeration; change the logic so both failure paths throw the same exception and message (e.g., "Invalid credentials" or a custom AuthenticationException) when either repository.findByEmail returns empty or passwordEncoder.matches(...) is false; update references in the method handling UserEntity so both branches use the same exception type and text and remove any branch-specific messages.
71-73:⚠️ Potential issue | 🟠 Major
existsByEmail+saveis still race-prone under concurrency.Two concurrent requests can pass Line 71 and race on Line 80. Keep DB uniqueness as source of truth and translate constraint violations to your domain error.
Also applies to: 80-80
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` around lines 71 - 73, The current pre-check using repository.existsByEmail(...) in AuthService is race-prone; remove the optimistic exists check and instead rely on the DB unique constraint when saving (repository.save(...)) and catch the persistence exception (e.g., DataIntegrityViolationException or ConstraintViolationException) around the save call, map that exception to your domain IllegalStateException (or a custom DuplicateEmailException) with the same user-facing message, and keep other validation logic intact; update the method that calls repository.save(...) to wrap the save in a try/catch and translate the DB constraint violation into the same domain error so concurrent requests are handled safely.
117-117:⚠️ Potential issue | 🔴 CriticalReject all non-
ACTIVErefresh tokens and claim token state atomically.Current logic only blocks
USED. AREVOKEDtoken can still pass and rotate. Also, read-check-write here is race-prone for concurrent refresh attempts.Also applies to: 131-133
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java` at line 117, Replace the naive in-memory status check (refreshToken.getStatus() == StatusRefreshToken.USED) with a strict allow-only-StatusRefreshToken.ACTIVE policy and perform an atomic database claim: change the logic in AuthService to reject any token whose status is not ACTIVE, and instead of doing a read-then-write on refreshToken, add/use a repository method that atomically updates the token status to USED (or CLAIMED) with a WHERE id = :id AND status = :ACTIVE and validate the affected-row count == 1; if the update returns 0, throw the appropriate refresh-failure exception. Apply the same pattern to the other occurrence noted around lines 131-133 so all refresh token rotations use the atomic "update where status = ACTIVE" claim before proceeding.backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java (1)
66-68:⚠️ Potential issue | 🔴 Critical
@ExceptionHandlersignature is invalid for one declared exception type.Line 66 includes
IllegalStateException, but Line 67 accepts onlyAuthenticationException. Spring cannot bindIllegalStateExceptionto that parameter type, so this path can break at runtime.🔧 Suggested fix
- `@ExceptionHandler`({AuthenticationException.class, IllegalStateException.class}) - public ResponseEntity<ErrorResponseDto> handleAuthException(AuthenticationException e) { + `@ExceptionHandler`(AuthenticationException.class) + public ResponseEntity<ErrorResponseDto> handleAuthException(AuthenticationException e) { log.warn("Unauthenticated: {}", e.getMessage()); return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); } + + `@ExceptionHandler`(IllegalStateException.class) + public ResponseEntity<ErrorResponseDto> handleIllegalStateAuthFailure(IllegalStateException e) { + log.warn("Unauthenticated: {}", e.getMessage()); + return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); + }In Spring MVC, if an `@ExceptionHandler` declares {AuthenticationException.class, IllegalStateException.class}, can the method parameter be typed only as AuthenticationException?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java` around lines 66 - 68, The `@ExceptionHandler` on handleAuthException declares both AuthenticationException and IllegalStateException but its parameter is typed only as AuthenticationException; update the handler so the parameter can accept both exception types (e.g., change the parameter type to Exception or RuntimeException, or create a second handler method for IllegalStateException). Specifically, edit the handleAuthException method signature in GlobalExceptionHandler (method name: handleAuthException) to accept a supertype like Exception (or add a new `@ExceptionHandler`(IllegalStateException.class) method) and adjust the log/response code to handle the general exception accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Line 24: Remove the dead commented security annotation by deleting the
commented-out "@PreAuthorize()" line in the MainController class; alternatively,
if security is intended, replace it with a valid expression such as a proper
SpEL like `@PreAuthorize`("hasRole('ROLE_USER')") on the controller class or the
specific handler method (MainController) so the intent is explicit and the code
is not left as commented-out noise.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 20-23: Remove the unresolved TODO comments from the
GlobalExceptionHandler class and either implement a small, concrete default
mapping or replace them with a single tracked-task reference; specifically add
handlers in GlobalExceptionHandler such as handleGenericException (Exception ->
500), handleRuntimeException (RuntimeException -> 500),
handleValidationException (MethodArgumentNotValidException -> 400),
handleBadRequest (HttpMessageNotReadableException -> 400) and handleAccessDenied
(AccessDeniedException -> 403) that return a consistent error response body
(timestamp, path, message, errorCode), or if implementation is deferred, replace
the multiple TODOs with one concise TODO referencing an issue/ID so the contract
isn’t left ambiguously documented.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`:
- Around line 23-34: The bulk UPDATE method setStatusUsedRefreshToken in
RefreshTokenRepository is missing `@Modifying`(clearAutomatically = true,
flushAutomatically = true), which can leave stale entities in the persistence
context; update the `@Modifying` annotation on setStatusUsedRefreshToken to
include clearAutomatically = true and flushAutomatically = true so the
persistence context is flushed and cleared automatically after the bulk update.
- Around line 18-21: The JPQL in setStatusByRefreshToken has an extra space
before WHERE and its `@Modifying` uses clearAutomatically=true and
flushAutomatically=true while setStatusUsedRefreshToken uses a plain `@Modifying`;
fix by removing the stray space so the query reads "... r.status = :status WHERE
r.tokenHash = :tokenHash" and make the `@Modifying` annotations consistent between
setStatusByRefreshToken and setStatusUsedRefreshToken (either remove
clearAutomatically/flushAutomatically from setStatusByRefreshToken or add the
same attributes to setStatusUsedRefreshToken) so both update methods behave
uniformly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java`:
- Around line 60-66: JwtException thrown by parsing methods in JwtService (via
jwtService.extractUsername(...) and jwtService.isTokenValid(...)) currently
bubbles up and yields HTTP 500; update handling to return 401 by either adding a
try-catch around calls in JwtAuthenticationFilter (catch
io.jsonwebtoken.JwtException and set the response status to 401 and stop filter
chain) or by adding an explicit handler in GlobalExceptionHandler (annotate a
method with `@ExceptionHandler`(io.jsonwebtoken.JwtException.class) that returns
ResponseEntity.status(401) with an appropriate body); reference
JwtAuthenticationFilter, jwtService.extractUsername, jwtService.isTokenValid,
GlobalExceptionHandler and JwtException when making the change so the
invalid/expired token paths always produce HTTP 401.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java`:
- Around line 33-40: In RefreshTokenService.hashRefreshToken, add a log
statement using the class's `@Slf4j` logger inside the NoSuchAlgorithmException
catch block to record the exception and context before rethrowing the
RuntimeException; locate the catch for NoSuchAlgorithmException in
hashRefreshToken, call log.error(...) with a clear message (e.g., "SHA-256
algorithm not found while hashing refresh token") and include the exception,
then rethrow the RuntimeException as currently implemented.
- Around line 27-31: The generateRefreshToken method currently constructs a new
SecureRandom on every call; make SecureRandom a class-level static final field
(e.g., private static final SecureRandom SECURE_RANDOM) in RefreshTokenService
and use that instance inside generateRefreshToken to call
nextBytes(randomBytes), avoiding per-call allocation and improving performance
and entropy reuse.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Line 111: Remove any logging of the refresh-token identifier (tokenHash) in
AuthService: eliminate or replace calls like log.info(tokenHash) with
non-sensitive diagnostics (e.g., log.debug("processed refresh token for
userId={}", userId) or no log at all) so that tokenHash is never written to
logs; ensure you update all occurrences (the log.info/log.debug lines
referencing tokenHash) in the AuthService methods accordingly.
---
Duplicate comments:
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java`:
- Line 32: Replace the numeric HTTP status literal in AuthController's register
response with the typed constant: change the ResponseEntity.status(201) call in
AuthController (method that returns register response) to use HttpStatus.CREATED
so the controller returns
ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request))
for clarity and consistency.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`:
- Line 26: The user-facing response in MainController's endpoint returns the
truncated string "You are auth"; update the return value in the method inside
class MainController to a complete, user-friendly message such as "You are
authenticated." (or "You are authorized." if appropriate for the endpoint's
semantics) by replacing the truncated string in that return statement to the
full phrase.
- Around line 17-20: In MainController (method handling userAccess) stop logging
raw principal identifiers at INFO: change the two log.info(...) calls that emit
principal.getName() to a lower level (debug) and avoid writing the raw
identifier; instead log a masked or hashed form of principal.getName() (or drop
the value entirely) so only non-sensitive context is emitted. Update the logging
calls around principal.getName() to use log.debug and replace the raw value with
a mask/hash or remove it, keeping the return of principal.getName() unchanged if
needed by callers.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`:
- Around line 66-68: The `@ExceptionHandler` on handleAuthException declares both
AuthenticationException and IllegalStateException but its parameter is typed
only as AuthenticationException; update the handler so the parameter can accept
both exception types (e.g., change the parameter type to Exception or
RuntimeException, or create a second handler method for IllegalStateException).
Specifically, edit the handleAuthException method signature in
GlobalExceptionHandler (method name: handleAuthException) to accept a supertype
like Exception (or add a new `@ExceptionHandler`(IllegalStateException.class)
method) and adjust the log/response code to handle the general exception
accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java`:
- Line 11: In UserRepository, rename the method parameter in findByEmail from
"login" to "email" to match the method name and avoid confusion at call sites;
update the method signature in the UserRepository interface (findByEmail) so the
parameter is named email and ensure any implementing classes or usages that
reference the parameter name (e.g., in method references or tests) are adjusted
accordingly.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`:
- Line 121: The throw in AuthService (the statement "throw new
SecurityException(\"Token reuse detected\")") is resolving to
java.lang.SecurityException; change it to throw your app's exception by
importing com.smartjam.smartjamapi.exception.SecurityException at the top of
AuthService or by using the fully-qualified name
com.smartjam.smartjamapi.exception.SecurityException in the throw expression so
the service triggers your project's auth-exception handling path.
- Around line 44-49: AuthService currently throws different exceptions for
missing users (repository.findByEmail -> NoSuchElementException) and bad
passwords (passwordEncoder.matches -> IllegalStateException), enabling account
enumeration; change the logic so both failure paths throw the same exception and
message (e.g., "Invalid credentials" or a custom AuthenticationException) when
either repository.findByEmail returns empty or passwordEncoder.matches(...) is
false; update references in the method handling UserEntity so both branches use
the same exception type and text and remove any branch-specific messages.
- Around line 71-73: The current pre-check using repository.existsByEmail(...)
in AuthService is race-prone; remove the optimistic exists check and instead
rely on the DB unique constraint when saving (repository.save(...)) and catch
the persistence exception (e.g., DataIntegrityViolationException or
ConstraintViolationException) around the save call, map that exception to your
domain IllegalStateException (or a custom DuplicateEmailException) with the same
user-facing message, and keep other validation logic intact; update the method
that calls repository.save(...) to wrap the save in a try/catch and translate
the DB constraint violation into the same domain error so concurrent requests
are handled safely.
- Line 117: Replace the naive in-memory status check (refreshToken.getStatus()
== StatusRefreshToken.USED) with a strict allow-only-StatusRefreshToken.ACTIVE
policy and perform an atomic database claim: change the logic in AuthService to
reject any token whose status is not ACTIVE, and instead of doing a
read-then-write on refreshToken, add/use a repository method that atomically
updates the token status to USED (or CLAIMED) with a WHERE id = :id AND status =
:ACTIVE and validate the affected-row count == 1; if the update returns 0, throw
the appropriate refresh-failure exception. Apply the same pattern to the other
occurrence noted around lines 131-133 so all refresh token rotations use the
atomic "update where status = ACTIVE" claim before proceeding.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java`:
- Around line 30-31: UploadService currently holds static S3Client and
S3Presigner fields which break Spring lifecycle and testability; change them to
instance fields and remove static usage, create a `@Configuration` class (e.g.,
S3Config) that defines S3Client and S3Presigner `@Bean` methods using configured
endpoint/region/credentials, inject those beans into UploadService via
constructor injection, and add a `@PreDestroy` method or rely on Spring-managed
bean lifecycle to close resources so clients are cleanly shut down.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6a51a9de-3683-4e74-8ce0-187e077048ca
📒 Files selected for processing (14)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.javabackend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
| } | ||
|
|
||
| @GetMapping("/hello") | ||
| // @PreAuthorize() |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove commented-out @PreAuthorize (or enable it with a real expression).
Line 24 leaves dead/commented security code in place, which makes intent unclear.
🧹 Proposed cleanup
- // `@PreAuthorize`()📝 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.
| // @PreAuthorize() |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java`
at line 24, Remove the dead commented security annotation by deleting the
commented-out "@PreAuthorize()" line in the MainController class; alternatively,
if security is intended, replace it with a valid expression such as a proper
SpEL like `@PreAuthorize`("hasRole('ROLE_USER')") on the controller class or the
specific handler method (MainController) so the intent is explicit and the code
is not left as commented-out noise.
| // TODO: Это базовый шаблон для обработки ошибок | ||
| // TODO: Возможно, некоторые исключения ловятся неправильно | ||
| // TODO: Нужно будет потом уточнить маппинг (какое исключение -> какой статус) | ||
|
|
There was a problem hiding this comment.
Remove unresolved TODOs from the global error contract.
These TODOs indicate the handler mapping is knowingly incomplete; that’s risky in a shared API boundary.
I can draft a concrete exception→status/error-code matrix and corresponding handler skeleton if you want me to open a follow-up issue.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java`
around lines 20 - 23, Remove the unresolved TODO comments from the
GlobalExceptionHandler class and either implement a small, concrete default
mapping or replace them with a single tracked-task reference; specifically add
handlers in GlobalExceptionHandler such as handleGenericException (Exception ->
500), handleRuntimeException (RuntimeException -> 500),
handleValidationException (MethodArgumentNotValidException -> 400),
handleBadRequest (HttpMessageNotReadableException -> 400) and handleAccessDenied
(AccessDeniedException -> 403) that return a consistent error response body
(timestamp, path, message, errorCode), or if implementation is deferred, replace
the multiple TODOs with one concise TODO referencing an issue/ID so the contract
isn’t left ambiguously documented.
| @Transactional | ||
| @Modifying(clearAutomatically = true, flushAutomatically = true) | ||
| @Query("UPDATE RefreshTokenEntity r SET r.status = :status WHERE r.tokenHash = :tokenHash") | ||
| void setStatusByRefreshToken(@Param("tokenHash") String tokenHash, @Param("status") StatusRefreshToken status); |
There was a problem hiding this comment.
Fix double space in JPQL query and consider consistency.
There's an extra space before WHERE in the query. Also, this method uses clearAutomatically = true, flushAutomatically = true but the other update method (setStatusUsedRefreshToken) uses plain @Modifying. Consider making them consistent.
🐛 Proposed fix
`@Transactional`
`@Modifying`(clearAutomatically = true, flushAutomatically = true)
- `@Query`("UPDATE RefreshTokenEntity r SET r.status = :status WHERE r.tokenHash = :tokenHash")
+ `@Query`("UPDATE RefreshTokenEntity r SET r.status = :status WHERE r.tokenHash = :tokenHash")
void setStatusByRefreshToken(`@Param`("tokenHash") String tokenHash, `@Param`("status") StatusRefreshToken status);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`
around lines 18 - 21, The JPQL in setStatusByRefreshToken has an extra space
before WHERE and its `@Modifying` uses clearAutomatically=true and
flushAutomatically=true while setStatusUsedRefreshToken uses a plain `@Modifying`;
fix by removing the stray space so the query reads "... r.status = :status WHERE
r.tokenHash = :tokenHash" and make the `@Modifying` annotations consistent between
setStatusByRefreshToken and setStatusUsedRefreshToken (either remove
clearAutomatically/flushAutomatically from setStatusByRefreshToken or add the
same attributes to setStatusUsedRefreshToken) so both update methods behave
uniformly.
| @Modifying | ||
| @Transactional | ||
| @Query(""" | ||
| UPDATE RefreshTokenEntity r | ||
| SET r.status = :newStatus | ||
| WHERE r.user = :user | ||
| AND r.status = :currentStatus | ||
| """) | ||
| void setStatusUsedRefreshToken( | ||
| @Param("user") UserEntity userEntity, | ||
| @Param("currentStatus") StatusRefreshToken currentStatus, | ||
| @Param("newStatus") StatusRefreshToken newStatus); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add clearAutomatically and flushAutomatically for consistency.
The first update method uses these options but this one doesn't, which could leave stale entities in the persistence context after the bulk update.
♻️ Proposed fix
- `@Modifying`
+ `@Modifying`(clearAutomatically = true, flushAutomatically = true)
`@Transactional`
`@Query`("""
UPDATE RefreshTokenEntity r🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java`
around lines 23 - 34, The bulk UPDATE method setStatusUsedRefreshToken in
RefreshTokenRepository is missing `@Modifying`(clearAutomatically = true,
flushAutomatically = true), which can leave stale entities in the persistence
context; update the `@Modifying` annotation on setStatusUsedRefreshToken to
include clearAutomatically = true and flushAutomatically = true so the
persistence context is flushed and cleared automatically after the bulk update.
| private Claims extractAllClaims(String token) { | ||
| return Jwts.parser() | ||
| .verifyWith((SecretKey) getSigningKey()) | ||
| .build() | ||
| .parseSignedClaims(token) | ||
| .getPayload(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for JwtException handling in JwtAuthenticationFilter and GlobalExceptionHandler
rg -n "JwtException|ExpiredJwtException|MalformedJwtException" --type java -C 3Repository: Satlykovs/SmartJam
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Find JwtAuthenticationFilter
fd -e java | xargs grep -l "JwtAuthenticationFilter\|class.*Filter" | head -20Repository: Satlykovs/SmartJam
Length of output: 246
🏁 Script executed:
#!/bin/bash
# Search for GlobalExceptionHandler, ControllerAdvice, or similar exception handling
rg -n "@ControllerAdvice|@ExceptionHandler|GlobalExceptionHandler" --type java -C 2Repository: Satlykovs/SmartJam
Length of output: 5765
🏁 Script executed:
#!/bin/bash
# Find where parseSignedClaims or extractAllClaims is called
rg -n "extractAllClaims|parseSignedClaims" --type java -C 3Repository: Satlykovs/SmartJam
Length of output: 1820
🏁 Script executed:
#!/bin/bash
# Look for the JwtService class to understand its full context
fd -e java JwtService | xargs cat -nRepository: Satlykovs/SmartJam
Length of output: 3155
🏁 Script executed:
#!/bin/bash
# Read JwtAuthenticationFilter
cat -n backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.javaRepository: Satlykovs/SmartJam
Length of output: 2794
🏁 Script executed:
#!/bin/bash
# Check the class hierarchy of JwtException from JJWT library
# Search for try-catch blocks around JWT operations
rg -n "try|catch|JwtException" backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/ --type java -C 2Repository: Satlykovs/SmartJam
Length of output: 1205
Handle JwtException to return 401 instead of 500 for invalid/expired tokens.
JwtAuthenticationFilter calls jwtService.extractUsername(jwt) and isTokenValid() without exception handling. Both methods invoke parseSignedClaims, which throws JwtException (including ExpiredJwtException, MalformedJwtException) on invalid tokens. Since JwtException does not extend Spring's AuthenticationException, it falls through to GlobalExceptionHandler's generic @ExceptionHandler(Exception.class) which returns HTTP 500. Add try-catch in JwtAuthenticationFilter to catch JWT exceptions and respond with 401, or add explicit @ExceptionHandler(JwtException.class) to GlobalExceptionHandler returning 401.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java`
around lines 60 - 66, JwtException thrown by parsing methods in JwtService (via
jwtService.extractUsername(...) and jwtService.isTokenValid(...)) currently
bubbles up and yields HTTP 500; update handling to return 401 by either adding a
try-catch around calls in JwtAuthenticationFilter (catch
io.jsonwebtoken.JwtException and set the response status to 401 and stop filter
chain) or by adding an explicit handler in GlobalExceptionHandler (annotate a
method with `@ExceptionHandler`(io.jsonwebtoken.JwtException.class) that returns
ResponseEntity.status(401) with an appropriate body); reference
JwtAuthenticationFilter, jwtService.extractUsername, jwtService.isTokenValid,
GlobalExceptionHandler and JwtException when making the change so the
invalid/expired token paths always produce HTTP 401.
| public String generateRefreshToken() { | ||
| byte[] randomBytes = new byte[64]; | ||
| new SecureRandom().nextBytes(randomBytes); | ||
| return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Reuse SecureRandom instance instead of creating a new one per call.
Creating a new SecureRandom for each token generation is inefficient. Make it a class-level static final field.
♻️ Proposed fix
`@Service`
`@Slf4j`
public class RefreshTokenService {
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
`@Getter`
`@Value`("${security.jwt.expiration-time-refresh}")
private long refreshExpiration;
public String generateRefreshToken() {
byte[] randomBytes = new byte[64];
- new SecureRandom().nextBytes(randomBytes);
+ SECURE_RANDOM.nextBytes(randomBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java`
around lines 27 - 31, The generateRefreshToken method currently constructs a new
SecureRandom on every call; make SecureRandom a class-level static final field
(e.g., private static final SecureRandom SECURE_RANDOM) in RefreshTokenService
and use that instance inside generateRefreshToken to call
nextBytes(randomBytes), avoiding per-call allocation and improving performance
and entropy reuse.
| public static String hashRefreshToken(String token) { | ||
| try { | ||
| byte[] digest = MessageDigest.getInstance("SHA-256").digest(token.getBytes(StandardCharsets.UTF_8)); | ||
| return new String(Hex.encode(digest)); | ||
| } catch (NoSuchAlgorithmException e) { | ||
| throw new RuntimeException("SHA-256 algorithm not found", e); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider logging the error before rethrowing.
The NoSuchAlgorithmException catch rethrows as RuntimeException. While SHA-256 is always available on standard JVMs, if this ever fails, the @Slf4j logger could be used to log the error for diagnostics.
♻️ Proposed fix
} catch (NoSuchAlgorithmException e) {
+ log.error("SHA-256 algorithm not available", e);
throw new RuntimeException("SHA-256 algorithm not found", e);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java`
around lines 33 - 40, In RefreshTokenService.hashRefreshToken, add a log
statement using the class's `@Slf4j` logger inside the NoSuchAlgorithmException
catch block to record the exception and context before rethrowing the
RuntimeException; locate the catch for NoSuchAlgorithmException in
hashRefreshToken, call log.error(...) with a clear message (e.g., "SHA-256
algorithm not found while hashing refresh token") and include the exception,
then rethrow the RuntimeException as currently implemented.
|
|
||
| String tokenHash = RefreshTokenService.hashRefreshToken(refreshTokenRequest.refreshToken()); | ||
|
|
||
| log.info(tokenHash); |
There was a problem hiding this comment.
Do not log refresh-token hashes.
Even hashed token identifiers are sensitive authentication artifacts; logging them increases leakage risk.
🧹 Suggested fix
- log.info(tokenHash);
+ log.debug("Refreshing token");
@@
- log.error(tokenHash);
+ log.warn("Refresh token reuse detected");
@@
- log.error(tokenHash);
+ log.warn("Expired refresh token");Also applies to: 119-119, 126-126
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java`
at line 111, Remove any logging of the refresh-token identifier (tokenHash) in
AuthService: eliminate or replace calls like log.info(tokenHash) with
non-sensitive diagnostics (e.g., log.debug("processed refresh token for
userId={}", userId) or no log at all) so that tokenHash is never written to
logs; ensure you update all occurrences (the log.info/log.debug lines
referencing tokenHash) in the AuthService methods accordingly.
…228/feature/auth # Conflicts: # backend/smartjam-api/build.gradle # backend/smartjam-api/src/main/resources/application.yaml
Summary by CodeRabbit
Release Notes