Skip to content

Draft: Implement Auth#2

Open
men229 wants to merge 14 commits intomainfrom
men228/feature/auth
Open

Draft: Implement Auth#2
men229 wants to merge 14 commits intomainfrom
men228/feature/auth

Conversation

@men229
Copy link
Collaborator

@men229 men229 commented Mar 4, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Added user authentication with login and registration endpoints.
    • Implemented secure password hashing and validation for user accounts.
    • Configured endpoint security to protect resources and enforce authentication.
    • Added comprehensive error handling with structured error responses and detailed messaging.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • backend/smartjam-api/src/main/resources/application.yaml
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 69a12c06-bd58-411c-9927-cb1c1deabe18

📥 Commits

Reviewing files that changed from the base of the PR and between 535bcf8 and 85e9f77.

📒 Files selected for processing (1)
  • backend/smartjam-api/src/main/resources/application.yaml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Build / Dependencies
backend/smartjam-api/build.gradle
Added Spring Security & Validation, JJWT (api/impl/jackson), MapStruct (processor), and AWS S3 SDK; minor Gradle call style change.
Security Config & Beans
backend/smartjam-api/src/main/java/.../SecurityConfig.java, .../config/ApplicationConfig.java
New SecurityFilterChain, CSRF disabled, /api/auth/** permitted, session stateless; beans for UserDetailsService and BCrypt PasswordEncoder.
Auth Controllers
.../controller/AuthController.java, .../controller/MainController.java
Added /api/auth endpoints (login/register/refresh) and secured /secured/* endpoints.
Auth Domain Services
.../service/AuthService.java, .../security/RefreshTokenService.java
AuthService implements login/register/refresh flows, persists and manages refresh token lifecycle; RefreshTokenService generates & hashes tokens.
JWT & Filter
.../security/JwtService.java, .../security/JwtAuthenticationFilter.java, .../security/UserDetailsImpl.java, .../config/CustomUserDetailsService.java
JWT generation/validation, extraction of username, filter to populate SecurityContext, and UserDetails bridge.
Entities & Repositories
.../entity/UserEntity.java, .../entity/RefreshTokenEntity.java, .../repository/UserRepository.java, .../repository/RefreshTokenRepository.java
Added User and RefreshToken JPA entities and repositories; token status update JPQL methods.
Upload (S3/MinIO)
.../controller/UploadController.java, .../service/UploadService.java, .../dto/UploadRequest.java, .../dto/UploadUrlResponse.java
Controller and service to create S3Client/S3Presigner, ensure bucket, and return 10-minute presigned PUT URLs.
DTOs & Mapping
backend/smartjam-api/src/main/java/.../dto/*, .../mapper/UserMapper.java
Added record DTOs (LoginRequest, RegisterRequest, RefreshTokenRequest, AuthResponse, ErrorResponseDto, UploadRequest/UploadUrlResponse) and MapStruct mapper for RegisterRequest→UserEntity.
Enums & Error Types
backend/smartjam-api/src/main/java/.../enums/*, .../exception/*.java
Added enums (Role, StatusRefreshToken, AvailabilityStatus, ErrorCode) and exceptions (TokenNotFoundException, TokenExpiredException, SecurityException).
Global Exception Handling
backend/smartjam-api/src/main/java/.../exception/GlobalExceptionHandler.java
Centralized @RestControllerAdvice mapping various exceptions to consistent ErrorResponseDto payloads and HTTP statuses.

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)
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐇 I hop through code and hide a key,
I bake fresh tokens behind a tree,
Filters guard the burrow tight,
Presigned links launch files in flight,
Hooray — secure carrots for all to see!

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess whether the description is related to the changeset. Add a clear pull request description that explains the purpose, scope, and key changes introduced by this authentication implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Draft: Implement Auth' is vague and uses non-descriptive terms. While it mentions 'Auth', it doesn't specify the scope or main change (e.g., adding authentication service, JWT, login/register endpoints). Make the title more specific and descriptive, such as 'Add JWT-based authentication with login, register, and refresh endpoints' or 'Implement Spring Security with JWT authentication flow'.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch men228/feature/auth

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 email instead of login in Line 10 for clarity and consistency with findByEmail.

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: Prefer HttpStatus.CREATED over numeric literal for response code.

Line 38 should use the semantic constant instead of 201 to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 41d52f3 and 9e982db.

📒 Files selected for processing (13)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java

Comment on lines +6 to +8
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
Copy link

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.

Suggested change
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

Copy link
Owner

Choose a reason for hiding this comment

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

Он прав, версия устарела, используй 0.13.0, можешь смотреть версии на https://mvnrepository.com/

Copy link

Choose a reason for hiding this comment

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

@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'

( ˶ᵔ ᵕ ᵔ˶ )
🐇

Comment on lines +3 to +6
public record LoginRequest(
String email,
String password
) {
Copy link

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "LoginRequest.java" -type f

Repository: Satlykovs/SmartJam

Length of output: 145


🏁 Script executed:

find . -name "pom.xml" -o -name "build.gradle" | head -5

Repository: Satlykovs/SmartJam

Length of output: 161


🏁 Script executed:

find . -path "*/controller*" -name "*.java" -type f | grep -i login

Repository: Satlykovs/SmartJam

Length of output: 44


🏁 Script executed:

cat -n ./backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java

Repository: Satlykovs/SmartJam

Length of output: 221


🏁 Script executed:

cat ./backend/smartjam-api/build.gradle

Repository: Satlykovs/SmartJam

Length of output: 802


🏁 Script executed:

find ./backend/smartjam-api -path "*/controller*" -name "*.java" -type f

Repository: Satlykovs/SmartJam

Length of output: 154


🏁 Script executed:

cat -n ./backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java

Repository: 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.

Suggested change
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.

Copy link
Owner

Choose a reason for hiding this comment

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

Ты используешь валидацию в контроллере, но пока не накинул аннотаций в DTO-шке, не забудь

Copy link

Choose a reason for hiding this comment

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

@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
) {
}

Comment on lines +3 to +7
public record RegisterRequest(
String username,
String email,
String password
) {
Copy link

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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.

Copy link
Owner

Choose a reason for hiding this comment

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

Ты используешь валидацию в контроллере, но пока не накинул аннотаций в DTO-шке, не забудь

Copy link

Choose a reason for hiding this comment

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

@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, так что дополнительных изменений в конфигурации не потребуется.

Comment on lines +28 to +33
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");
}
Copy link

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

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");
}
🤖 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.

Copy link
Owner

Choose a reason for hiding this comment

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

Он прав, ты по факту помогаешь хацкерам получить инфу, т.к у тебя в случае если аккаунт верный, то полетит другая ошибка. Лучше всегда возвращать унифицировванную ошибку "Неверный логин или пароль"

Copy link

Choose a reason for hiding this comment

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

@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.

(\_/)
(•‿•)
(> 🔒

Comment on lines +39 to +51
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);
Copy link

@coderabbitai coderabbitai bot Mar 4, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 5

Repository: 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.

Suggested change
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.

Copy link
Owner

@Satlykovs Satlykovs Mar 4, 2026

Choose a reason for hiding this comment

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

Он прав, тут может быть Race Condition, у тебя 2 потока могут проверить isPresent() и одновременно попытаться сохранить юзера, тогда всё равно полетит ошибка, так что isPresent() вообще не защищает здесь. Как будто можно просто делать save() и отлавливать ошибку, если уже пользователь существует (например в exception handler-e, который у тебя есть)

Copy link

Choose a reason for hiding this comment

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

@Satlykovs, именно так! Подход "check-then-save" не является атомарным, поэтому не защищает от гонки. Правильное решение — убрать isPresent() и обернуть save() в try-catch, перехватывая DataIntegrityViolationException (которую Spring Data бросает при нарушении уникального ограничения на уровне БД) и преобразуя её в нужное сообщение об ошибке. Предложенный выше committable suggestion уже содержит готовую реализацию этого подхода. 👆

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
backend/smartjam-api/build.gradle (1)

6-8: ⚠️ Potential issue | 🟠 Major

Upgrade 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e982db and b4530b8.

📒 Files selected for processing (1)
  • backend/smartjam-api/build.gradle


var errorDto = new ErrorResponseDto(
"INTERNAL_SERVER_ERROR",
e.getMessage(),
Copy link
Owner

Choose a reason for hiding this comment

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

Ты выдаешь наружу приватную информацию, так нельзя делать


import java.util.Optional;

public interface AuthRepository extends JpaRepository<UserEntity, Long> {
Copy link
Owner

Choose a reason for hiding this comment

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

Репозиториии называют по той сущности, которой они управляют. Т.е тут не AuthRepository, а UserRepository

Еще, в схеме бд было указано, что ключ будет UUID, у тебя тут Long




public AuthService(AuthRepository repository, PasswordEncoder passwordEncoder) {
Copy link
Owner

@Satlykovs Satlykovs Mar 4, 2026

Choose a reason for hiding this comment

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

Используй аннотацию Lombok @requiredargsconstructor

userEntity.setUsername(request.username());
userEntity.setEmail(request.email());
userEntity.setPasswordHash(passwordEncoder.encode(request.password()));
userEntity.setRole("USER");
Copy link
Owner

Choose a reason for hiding this comment

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

Строку для роли использовать плохо, легко ошибиться в названии

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

не понял что тут хотят от меня

private String avatarUrl;

@Column(nullable = false)
private String role;
Copy link
Owner

Choose a reason for hiding this comment

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

Строку для роли использовать не найс, лучше Enum, в котором будут условные STUDENT и TEACHER, + накинуть аннотацию Enumerated(EnumType.STRING)

@Satlykovs
Copy link
Owner

P.S: я подключил spotless для форматирования кода, без этого тесты не пройдут, проверь у себя в IDE, там должна быть цель spotlessApply у gradle-а

Repository owner deleted a comment from coderabbitai bot Mar 4, 2026
@Satlykovs
Copy link
Owner

@coderabbitai please summarize all the feedback from this review (including my comments and yours) into a structured checklist for the author.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== PR Review Comments ==="
gh pr view 2 --repo Satlykovs/SmartJam --comments

echo ""
echo "=== PR Review Inline Comments ==="
gh api repos/Satlykovs/SmartJam/pulls/2/reviews --jq '.[] | {id: .id, user: .user.login, state: .state, body: .body}'

echo ""
echo "=== All Review Comment Threads ==="
gh api repos/Satlykovs/SmartJam/pulls/2/comments --jq '.[] | {user: .user.login, path: .path, line: .line, body: .body, diff_hunk: .diff_hunk}'

Length of output: 126099


@Satlykovs — here is the full structured checklist compiled from all review feedback (yours and mine):


✅ Auth PR — Review Checklist for the Author

🔴 Critical / Security

  • Account Enumeration (AuthService.java, lines 28–33): Login failure returns different exceptions for "user not found" vs "wrong password". Replace both with a single uniform error (e.g. "Неверный логин или пароль"). (flagged by both @Satlykovs and coderabbit)
  • Raw exception messages exposed to clients (GlobalExceptionHandler.java, lines 24–63): All handlers pass e.getMessage() directly into the response DTO. Replace with sanitized client-facing messages; keep raw details in server logs only. (flagged by both @Satlykovs and coderabbit)

🟠 Major / Architecture

  • JWT not wired (SecurityConfig.java): JWT dependencies are declared in build.gradle but completely unused. SecurityConfig uses HTTP Basic with stateful sessions. Implement: disable httpBasic(), set SessionCreationPolicy.STATELESS, add a JwtAuthenticationFilter, and generate/return a JWT token from AuthService.login() and AuthService.register(). (coderabbit)
  • Race condition in registration (AuthService.java, lines 39–51): The check-then-save pattern (findByEmail().isPresent()) is not atomic and does not protect against concurrent registrations. Remove the pre-check and wrap repository.save() in a try-catch for DataIntegrityViolationException. (flagged by both @Satlykovs and coderabbit)
  • Missing DTO validation annotations (LoginRequest.java, RegisterRequest.java): @Valid in the controller does nothing without constraints on record components. Add @NotBlank, @Email, @Size(min=8, max=72) as appropriate to both DTOs. (flagged by both @Satlykovs and coderabbit)
  • JJWT version outdated (build.gradle, lines 6–8): 0.11.5 is outdatedConfirmed addressed by @Satlykovs. Upgrade to 0.13.0 using the BOM. Note: 0.11.x0.13.x has breaking API changes.
  • main method missing public (SmartjamApiApplication.java, line 9): Package-private main breaks JVM startupAddressed in commits a61d61eb4530b8. Verify the fix is correct.

🟡 Code Quality / Conventions

  • Use @Slf4j (Lombok) (AuthController.java line 17, GlobalExceptionHandler.java line 18): Replace manual LoggerFactory.getLogger(...) declarations with the @Slf4j Lombok annotation. (Satlykovs)
  • Use @RequiredArgsConstructor (Lombok) (AuthController.java line 21, AuthService.java line 22): Remove manual constructors and use @RequiredArgsConstructor instead. (Satlykovs)
  • Rename AuthRepositoryUserRepository (AuthRepository.java): Repositories are named after the entity they manage. (Satlykovs)
  • id type should be UUID, not Long (UserEntity.java line 21, AuthRepository.java line 8): The DB schema specifies UUID as the primary key type. (Satlykovs)
  • role field should be an Enum, not a String (UserEntity.java line 42, AuthService.java line 49): Create an enum (e.g. with STUDENT, TEACHER values), annotate the field with @Enumerated(EnumType.STRING). (Satlykovs)
  • Extract buildResponse() helper in GlobalExceptionHandler (all handler methods): Add a private buildResponse(HttpStatus, String, Exception) method to eliminate duplicated ErrorResponseDto + ResponseEntity assembly. Also add @Builder to ErrorResponseDto. (Satlykovs + coderabbit)
  • Use Lombok @Builder for UserEntity construction (AuthService.java, lines 45–49): Replace the boilerplate setter chain with a builder. Long-term, consider MapStruct for DTO ↔ entity mapping. (Satlykovs)
  • Rename parameter loginemail (AuthRepository.java, line 10): The parameter name should match the method name findByEmail. (coderabbit)
  • Use HttpStatus.CREATED instead of 201 (AuthController.java, line 38): Prefer the semantic constant for readability. (coderabbit)
  • Clarify the purpose of AvailabilityStatus enum (AvailabilityStatus.java): @Satlykovs asked what this enum is for. Consider renaming or adding documentation if it's used for auth responses. (Satlykovs)

📋 Pre-merge / Process

  • Run spotlessApply before committing: Spotless is configured for code formatting and CI tests will fail without it. Run the spotlessApply Gradle goal in your IDE. (Satlykovs)
  • Docstring coverage: Currently at 0% (threshold: 80%). Add Javadoc to public methods to pass the pre-merge check.

🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-04T22:40:28.988Z
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.

@Satlykovs
Copy link
Owner

@coderabbitai please duplicate this list in russian language if you can

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Tip

For best results, initiate chat on the files or code changes.

@Satlykovs — вот полный чеклист на русском языке:


✅ Auth PR — Чеклист для автора

🔴 Критично / Безопасность

  • Утечка информации через аутентификацию (AuthService.java, строки 28–33): При неудачном входе выбрасываются разные исключения для случаев «пользователь не найден» и «неверный пароль». Замените оба на одну единообразную ошибку (например: "Неверный логин или пароль"). (отмечено и @Satlykovs, и coderabbit)
  • Сырые сообщения исключений передаются клиенту (GlobalExceptionHandler.java, строки 24–63): Все обработчики передают e.getMessage() напрямую в DTO ответа. Замените на санированные сообщения для клиента; сырые детали оставьте только в серверных логах. (отмечено и @Satlykovs, и coderabbit)

🟠 Важно / Архитектура

  • JWT не подключён (SecurityConfig.java): JWT-зависимости объявлены в build.gradle, но нигде не используются. SecurityConfig использует HTTP Basic со stateful-сессиями. Необходимо: отключить httpBasic(), установить SessionCreationPolicy.STATELESS, добавить JwtAuthenticationFilter, и генерировать/возвращать JWT-токен из AuthService.login() и AuthService.register(). (coderabbit)
  • Гонка состояний при регистрации (AuthService.java, строки 39–51): Паттерн «проверить, потом сохранить» (findByEmail().isPresent()) не является атомарным и не защищает от одновременных регистраций. Уберите предварительную проверку и оберните repository.save() в try-catch для DataIntegrityViolationException. (отмечено и @Satlykovs, и coderabbit)
  • Отсутствуют аннотации валидации на DTO (LoginRequest.java, RegisterRequest.java): @Valid в контроллере ничего не делает без ограничений на полях записи. Добавьте @NotBlank, @Email, @Size(min=8, max=72) там, где это уместно, в оба DTO. (отмечено и @Satlykovs, и coderabbit)
  • Устаревшая версия JJWT (build.gradle, строки 6–8): 0.11.5 устарелаПодтверждено исправленным @Satlykovs. Обновитесь до 0.13.0 через BOM. Примечание: переход 0.11.x0.13.x содержит breaking changes в API.
  • Отсутствует public у метода main (SmartjamApiApplication.java, строка 9): Package-private main нарушает запуск JVMИсправлено в коммитах a61d61eb4530b8. Проверьте корректность исправления.

🟡 Качество кода / Соглашения

  • Используйте @Slf4j (Lombok) (AuthController.java строка 17, GlobalExceptionHandler.java строка 18): Замените ручные объявления LoggerFactory.getLogger(...) на аннотацию @Slf4j из Lombok. (Satlykovs)
  • Используйте @RequiredArgsConstructor (Lombok) (AuthController.java строка 21, AuthService.java строка 22): Удалите ручные конструкторы и используйте @RequiredArgsConstructor. (Satlykovs)
  • Переименуйте AuthRepositoryUserRepository (AuthRepository.java): Репозитории называются по имени сущности, которой они управляют. (Satlykovs)
  • Тип поля id должен быть UUID, а не Long (UserEntity.java строка 21, AuthRepository.java строка 8): Схема БД предполагает UUID в качестве типа первичного ключа. (Satlykovs)
  • Поле role должно быть Enum, а не String (UserEntity.java строка 42, AuthService.java строка 49): Создайте enum (например, со значениями STUDENT, TEACHER), добавьте к полю аннотацию @Enumerated(EnumType.STRING). (Satlykovs)
  • Вынесите вспомогательный метод buildResponse() в GlobalExceptionHandler (все методы-обработчики): Добавьте приватный метод buildResponse(HttpStatus, String, Exception) для устранения дублирования сборки ErrorResponseDto и ResponseEntity. Также добавьте @Builder к ErrorResponseDto. (Satlykovs + coderabbit)
  • Используйте Lombok @Builder при создании UserEntity (AuthService.java, строки 45–49): Замените цепочку сеттеров на builder. В долгосрочной перспективе рассмотрите MapStruct для маппинга DTO ↔ entity. (Satlykovs)
  • Переименуйте параметр loginemail (AuthRepository.java, строка 10): Имя параметра должно соответствовать названию метода findByEmail. (coderabbit)
  • Используйте HttpStatus.CREATED вместо 201 (AuthController.java, строка 38): Предпочтительно использовать семантическую константу для читаемости. (coderabbit)
  • Уточните назначение enum AvailabilityStatus (AvailabilityStatus.java): @Satlykovs спрашивал, для чего этот enum. Рассмотрите переименование или добавление документации, если он используется для ответов авторизации. (Satlykovs)

📋 Перед слиянием / Процесс

  • Запустите spotlessApply перед коммитом: Spotless настроен для форматирования кода, и CI-тесты не пройдут без его применения. Запустите Gradle-цель spotlessApply в вашей IDE. (Satlykovs)
  • Покрытие документацией: В данный момент 0% (порог: 80%). Добавьте Javadoc к публичным методам для прохождения pre-merge проверки.

🧠 Learnings used
Learnt from: Satlykovs
Repo: Satlykovs/SmartJam PR: 0
File: :0-0
Timestamp: 2026-03-04T22:40:28.988Z
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 25

♻️ Duplicate comments (4)
backend/smartjam-api/build.gradle (1)

6-8: ⚠️ Potential issue | 🟠 Major

This appears to regress the previously agreed JJWT BOM upgrade.

Lines 6-8 still hard-pin 0.12.3 on each JJWT artifact. The PR checklist says this was supposed to move to jjwt-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 | 🟠 Major

Race condition on registration remains unaddressed.

The check-then-save pattern (findByEmail().isPresent() followed by save()) 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 catch DataIntegrityViolationException instead.

🤖 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 | 🔴 Critical

Account 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 | 🔵 Trivial

Use Lombok annotations for logger and constructor.

Replace manual logger and constructor with @Slf4j and @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

📥 Commits

Reviewing files that changed from the base of the PR and between b4530b8 and b35ec7f.

📒 Files selected for processing (18)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/resources/application.yaml

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (14)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java (1)

3-8: 🧹 Nitpick | 🔵 Trivial

Clarify the enum's purpose or rename it to reflect its usage.

The name AvailabilityStatus is 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 to AccountStatus or UserStatus, and add a Javadoc comment explaining its intended use in AuthResponse.

🤖 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 | 🔵 Trivial

Add audit timestamp fields.

Consider adding createdAt and updatedAt fields with @CreationTimestamp and @UpdateTimestamp annotations 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 | 🔵 Trivial

Consider using UUID for user identifiers.

Using Long with 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 | 🔵 Trivial

Align method and parameter names with query semantics.

  • findByEmail(String login): Parameter should be email, not login
  • findUserEntitiesByUsername: Should be findByUsername (Spring Data convention; "Entities" plural is unnecessary for Optional<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 | 🟠 Major

Add validation and use Java naming conventions.

This record lacks field-level validation constraints, making @Valid in the controller ineffective. The snake_case field name (refresh_token) is non-idiomatic Java. Use camelCase with @JsonProperty for 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 | 🟠 Major

Add validation constraints to enable request validation.

The @Valid annotation in AuthController requires 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 | 🟠 Major

Use CustomUserDetailsService as the single UserDetailsService source.

This file creates a second user-loading implementation even though backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java already 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 | 🟠 Major

Remove the check-then-save race from registration.

Lines 79-91 are not atomic: two concurrent requests can both pass isPresent() and then race into save(). Let the database enforce uniqueness and translate DataIntegrityViolationException into 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 | 🟠 Major

Return 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 | 🟠 Major

Give refresh tokens their own lifetime.

Lines 71, 101, and 128 all reuse jwtService.getJwtExpiration(), which is the access-token TTL used by JwtService.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 | 🟠 Major

Revoke 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 | 🟠 Major

Treat 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 | 🟠 Major

Add Bean Validation annotations to the record components.

Without component constraints, @Valid does not reject blank usernames, malformed emails, or short passwords here. Apply the same pattern to LoginRequest too 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 | 🟠 Major

Populate 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.java and method security depend on authorities, and JwtService.generateAccessToken() serializes them into the JWT. With List.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

📥 Commits

Reviewing files that changed from the base of the PR and between b35ec7f and be2f6c8.

📒 Files selected for processing (20)
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java

Comment on lines +55 to +63
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}

@Override
public @Nullable String getPassword() {
return passwordHash;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 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 2

Repository: Satlykovs/SmartJam

Length of output: 16021


🏁 Script executed:

cat -n backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java

Repository: Satlykovs/SmartJam

Length of output: 1904


🏁 Script executed:

cat -n backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java

Repository: 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 2

Repository: 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 3

Repository: 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.

Copy link
Owner

@Satlykovs Satlykovs left a comment

Choose a reason for hiding this comment

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

@men229, надо ботать алгосы

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (9)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java (2)

35-38: ⚠️ Potential issue | 🟡 Minor

Return 200 OK from getNewToken().

/refresh is not creating a permanent resource, so 201 Created is 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 /refresh should return 200 OK, not 201 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 | 🟡 Minor

Replace the magic status code in register().

201 is the right status here, but hard-coding it makes the controller less clear than HttpStatus.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 HttpStatus 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 - 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 | 🔵 Trivial

Consider separate handler for IllegalStateException returning 401.

Grouping IllegalStateException with bad request exceptions may mask authentication failures. If IllegalStateException is thrown for auth-related errors (e.g., invalid credentials), it should return 401 Unauthorized instead of 400 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 UNAUTHORIZED to the ErrorCode enum.

,

🤖 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 | 🔵 Trivial

Extract a private helper to reduce duplication.

All four handlers construct ErrorResponseDto identically. 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 | 🟠 Major

Method names still use noun "handler" instead of verb "handle".

Three methods still have inconsistent naming compared to handleNoHandlerFound on 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 | 🟠 Major

Rename NON_FOUND_PAGE to RESOURCE_NOT_FOUND.

The constant name is grammatically incorrect and semantically unclear. RESOURCE_NOT_FOUND better 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 | 🟠 Major

Catch JWT parse/validation failures inside the filter.

jwtService.extractUsername(jwt) and jwtService.isTokenValid(jwt, userDetails) can throw for malformed, expired, or tampered tokens. In the current flow that escapes doFilterInternal() 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 | 🟡 Minor

SpotlessCheck 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

📥 Commits

Reviewing files that changed from the base of the PR and between be2f6c8 and 352bf03.

📒 Files selected for processing (7)
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java

@men229 men229 force-pushed the men228/feature/auth branch from 5cffcdb to 64664f7 Compare March 12, 2026 22:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

♻️ Duplicate comments (24)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (3)

5-6: 🧹 Nitpick | 🔵 Trivial

Use Lombok @Slf4j to align with controller conventions.

Line [5]-Line [6] and Line [15] manually wire SLF4J, while AuthController already 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Clean up userAccess diagnostics and verify the null-branch contract.

Line [19] should not use System.out.println in controller code, and Line [24] should not log a successful call as error.
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 | 🟠 Major

Restore the agreed JJWT BOM/0.13.0 setup.

This re-pins JJWT to 0.12.3 instead of the earlier 0.13.0/BOM migration. Official JJWT docs still publish 0.13.0 as the install coordinates, and the project added a Maven BOM in 0.12.7 specifically 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 | 🟠 Major

Add validation constraints to enable @Valid in controller.

The record lacks validation annotations, so @Valid in AuthController has no effect. Add @NotBlank, @Email on email, and @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 | 🔵 Trivial

Use camelCase for record components with @JsonProperty for JSON mapping.

The refresh_token and access_token fields use snake_case, which is non-idiomatic Java. Use camelCase with @JsonProperty annotations.

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 | 🟠 Major

Add validation constraints to enable @Valid in controller.

Same issue as LoginRequest — missing validation annotations render @Valid ineffective. 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 | 🟠 Major

Add @NoArgsConstructor and @Builder for JPA compliance and cleaner construction.

JPA requires a no-arg constructor for entity instantiation. @Builder was also requested for use in AuthService.

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 | 🔵 Trivial

Clarify enum purpose or consider renaming.

The enum name AvailabilityStatus is ambiguous in an authentication context. The values (AVAILABLE, UNAVAILABLE, PENDING, BANNED) suggest user account status rather than availability. Consider renaming to AccountStatus or UserStatus to 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 | 🟠 Major

Rename class, add validation, and use camelCase.

Multiple issues already flagged:

  1. Class name TokenDto is vague — consider RefreshTokenRequest
  2. Missing @NotBlank validation
  3. Use camelCase with @JsonProperty
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 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 | 🟠 Major

Remove the second UserDetailsService bean.

Lines 22-30 create a repository-backed UserDetailsService that returns UserEntity, while CustomUserDetailsService already returns UserDetailsImpl. That splits authority/password mapping between two implementations and will break once UserEntity is decoupled from Spring Security. Inject CustomUserDetailsService here and wire it into DaoAuthenticationProvider directly.

♻️ 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 | 🟠 Major

Keep UserEntity as a pure JPA model.

backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java already exists as the Spring Security adapter. Keeping implements UserDetails here duplicates authority/password logic and is why UserDetailsImpl.build(...) currently depends on user.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 | 🟠 Major

Populate authorities in build() instead of returning List.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, read passwordHash directly so this class no longer depends on UserEntity implementing UserDetails.

🔐 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, so hasRole("ADMIN") will reject every request to this matcher. Either add ADMIN to Role or 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 | 🔵 Trivial

Avoid rebuilding cryptographic helpers on every call.

getSigningKey() decodes/rebuilds the key every call, and generateRefreshToken() creates a new SecureRandom per 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 | 🟠 Major

Add 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 | 🟠 Major

Registration 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 translate DataIntegrityViolationException.

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 | 🟠 Major

Unify login failure responses to prevent account enumeration.

Line 39 and Line 41 return different failure reasons (user missing vs invalid 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 | 🟠 Major

Refresh-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 | 🟠 Major

Rotate refresh token atomically and invalidate the old one.

getNewToken creates 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 | 🟠 Major

Handle 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 in try/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 | 🟠 Major

Do 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 | 🟠 Major

Separate auth-state failures from generic bad-request handling.

IllegalStateException is currently mapped to 400. In this PR, auth flows throw IllegalStateException for 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 | 🟡 Minor

Fix HTTP status semantics and remove magic number statuses.

Line 32 should use HttpStatus.CREATED (or ResponseEntity.status(HttpStatus.CREATED)), and Line 38 should return 200 OK (ResponseEntity.ok(...)) because /refresh does 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 /refresh should return 200 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

📥 Commits

Reviewing files that changed from the base of the PR and between be2f6c8 and 9fd741a.

📒 Files selected for processing (28)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
  • backend/smartjam-api/src/main/resources/application.yaml

Comment on lines +48 to +59
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();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (16)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java (2)

64-65: ⚠️ Potential issue | 🟡 Minor

Typo: presignedUtlpresignedUrl.

✏️ 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 | 🟠 Major

Missing input validation on fileName — potential path traversal risk.

The fileName is used directly as the S3 object key without sanitization. Malicious inputs like ../../../etc/passwd or 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 | 🟠 Major

Use UUID as the repository ID type.

UserEntity.id is UUID, but JpaRepository<UserEntity, Long> makes inherited CRUD methods type-incorrect and forces the extra findById(UUID) overload. Parameterize the repository with UUID and drop the custom findById.

🧩 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 | 🟠 Major

Don't treat every IllegalArgumentException as 401.

IllegalArgumentException is too generic for authentication failures. This mapping will turn unrelated bad input into UNAUTHORIZED, while IllegalStateException still falls through the bad-request handler. Route only the auth-specific exception type to 401—or swap this handler to IllegalStateException if that's what AuthService throws.

🔁 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 | 🟠 Major

Persist 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, with JwtService/AuthService extracting 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 | 🔵 Trivial

Use HttpStatus.CREATED instead of magic number 201.

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 | 🟡 Minor

Refresh endpoint should return 200 OK, not 201 Created.

The /refresh endpoint generates new tokens for an existing session—it's not creating a permanent resource. Based on learnings, token endpoints should return 200 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 | 🟡 Minor

Typo: "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 | 🟠 Major

Duplicate UserDetailsService bean registration.

CustomUserDetailsService is already annotated with @Service and implements UserDetailsService, making it a bean. This additional @Bean method creates a second UserDetailsService bean, which can cause NoUniqueBeanDefinitionException or unpredictable injection behavior. Remove this bean and inject CustomUserDetailsService directly 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 | 🟠 Major

Refresh 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 separate refreshTokenExpiration configuration.

#!/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 -i

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 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 | 🟠 Major

Race condition: existsByEmail check-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 | 🟠 Major

Old refresh token not invalidated after use—allows token replay.

When refreshing tokens, the old RefreshTokenEntity remains 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 | 🟠 Major

Use 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 | 🟠 Major

Implement 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 jti claim, storing only the jti in RefreshTokenEntity instead. This eliminates replay risk if the database is compromised. Currently, JwtService.generateRefreshToken() generates random Base64 strings with no JWT signature, and RefreshTokenEntity stores the full token.

Refactor to:

  1. Update RefreshTokenEntity: replace token field with jti field (UUID)
  2. Update JwtService.generateRefreshToken(): generate signed JWT with UUID jti claim instead of opaque random string
  3. Update RefreshTokenRepository: change findByToken(String) to findByJti(String) for lookups
  4. Update AuthService to extract jti from JWT after verification before storing

Applies 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 | 🟠 Major

Add 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 | 🟠 Major

Empty authorities break role-based authorization.

getAuthorities() returns an empty list, so any role-based security checks (e.g., @PreAuthorize("hasRole('...')"), .hasRole() in SecurityConfig) will always fail. Map the user's role to a GrantedAuthority.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd741a and e1ca78a.

📒 Files selected for processing (20)
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java

@Satlykovs
Copy link
Owner

Satlykovs commented Mar 16, 2026

@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.

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== build.gradle ==="
fd "build.gradle" --type f | xargs cat

echo ""
echo "=== SecurityConfig.java ==="
fd "SecurityConfig.java" | xargs cat

echo ""
echo "=== ApplicationConfig.java ==="
fd "ApplicationConfig.java" | xargs cat

echo ""
echo "=== CustomUserDetailsService.java ==="
fd "CustomUserDetailsService.java" | xargs cat

echo ""
echo "=== JwtService.java ==="
fd "JwtService.java" | xargs cat

echo ""
echo "=== JwtAuthenticationFilter.java ==="
fd "JwtAuthenticationFilter.java" | xargs cat

echo ""
echo "=== UserDetailsImpl.java ==="
fd "UserDetailsImpl.java" | xargs cat

echo ""
echo "=== AuthController.java ==="
fd "AuthController.java" | xargs cat

echo ""
echo "=== AuthService.java ==="
fd "AuthService.java" | xargs cat

echo ""
echo "=== MainController.java ==="
fd "MainController.java" | xargs cat

echo ""
echo "=== GlobalExceptionHandler.java ==="
fd "GlobalExceptionHandler.java" | xargs cat

echo ""
echo "=== UserEntity.java ==="
fd "UserEntity.java" | xargs cat

echo ""
echo "=== RefreshTokenEntity.java ==="
fd "RefreshTokenEntity.java" | xargs cat

echo ""
echo "=== UserRepository.java ==="
fd "UserRepository.java" | xargs cat

echo ""
echo "=== RefreshTokenRepository.java ==="
fd "RefreshTokenRepository.java" | xargs cat

echo ""
echo "=== LoginRequest.java ==="
fd "LoginRequest.java" | xargs cat

echo ""
echo "=== RegisterRequest.java ==="
fd "RegisterRequest.java" | xargs cat

echo ""
echo "=== AuthResponse.java ==="
fd "AuthResponse.java" | xargs cat

echo ""
echo "=== RefreshTokenRequest.java ==="
fd "RefreshTokenRequest.java" | xargs cat

echo ""
echo "=== ErrorResponseDto.java ==="
fd "ErrorResponseDto.java" | xargs cat

echo ""
echo "=== AvailabilityStatus.java ==="
fd "AvailabilityStatus.java" | xargs cat

echo ""
echo "=== Role.java ==="
fd "Role.java" | xargs cat

echo ""
echo "=== ErrorCode.java ==="
fd "ErrorCode.java" | xargs cat

echo ""
echo "=== UploadService.java ==="
fd "UploadService.java" | xargs cat

echo ""
echo "=== UploadController.java ==="
fd "UploadController.java" | xargs cat

echo ""
echo "=== UploadRequest.java ==="
fd "UploadRequest.java" | xargs cat

echo ""
echo "=== UploadUrlResponse.java ==="
fd "UploadUrlResponse.java" | xargs cat

echo ""
echo "=== SmartjamApiApplication.java ==="
fd "SmartjamApiApplication.java" | xargs cat

echo ""
echo "=== application.properties or application.yml ==="
fd "application.properties" -o -e yml --type f | xargs cat 2>/dev/null || true

echo ""
echo "=== Directory tree ==="
tree backend/ 2>/dev/null || find backend -type f -name "*.java" | sort

Length of output: 38414


@Satlykovs — here is the full, deep rescan. Every item links directly to the relevant file on GitHub.


🔍 Full Rescan — SmartJam Auth PR


🔴 P1 — Critical / Security


1. NoSuchElementException not handled → leaks 500 + stack trace

File: AuthService.java — Lines 28, 65, 71
File: GlobalExceptionHandler.java

AuthService throws NoSuchElementException, but GlobalExceptionHandler only catches EntityNotFoundException. The NoSuchElementException bubbles up to the generic Exception handler which returns HTTP 500 and logs the full stack trace. This also means a login with a non-existent email returns 500, while a wrong password returns 400 — a classic account enumeration vulnerability.

Fix: Add a handler for NoSuchElementException → 401, and unify all login error responses:

// GlobalExceptionHandler.java
`@ExceptionHandler`({NoSuchElementException.class, IllegalStateException.class})
public ResponseEntity<ErrorResponseDto> handleAuthError(RuntimeException e) {
    log.warn("Auth error: {}", e.getMessage()); // do NOT pass to client
    return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Invalid credentials");
}

Then in AuthService.login(), throw the same exception type for both failure cases.


2. Old refresh token never invalidated on rotation

File: AuthService.java — Lines 61–79

getNewToken() saves a new refresh token but never deletes the old one. A leaked or stolen refresh token stays valid forever and can be used an unlimited number of times.

Fix: Delete the old token before saving the new one:

refreshTokenRepository.delete(refreshToken); // delete old
// ... then save new
refreshTokenRepository.save(newRefreshTokenEntity);

3. register() not @Transactional — inconsistent DB state

File: AuthService.java — Line 39

register() performs two separate saves (userRepository.save() then refreshTokenRepository.save()). If the second save fails, the user record is already committed but no tokens are returned — the user is orphaned in the DB. login() and getNewToken() are @Transactional but register() is not.

Fix: Add @Transactional to register():

`@Transactional`
public AuthResponse register(RegisterRequest request) { ... }

4. Hardcoded credentials in UploadService

File: UploadService.java — Lines 30–31, 49–50

"minioadmin"/"minioadmin" and "http://localhost:9000" are hardcoded strings. These will never work in any non-local environment.

Fix: Move to application.yaml:

minio:
  endpoint: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin

Then inject with @Value("${minio.endpoint}"), etc.


🟠 P2 — Major / Database Consistency


5. ⚠️ Type mismatch in repositories — WILL BREAK AT RUNTIME

File: UserRepository.java — Line 8
File: RefreshTokenRepository.java — Line 8

Both entities use UUID as the primary key, but both repositories declare JpaRepository<Entity, Long>. This is a type mismatch that causes Spring Data to throw an error at startup or produce incorrect queries.

Additionally, UserRepository.findById(UUID id) attempts to shadow the JpaRepository method but with a different type — Spring Data will try to derive it as a field query, not a PK lookup.

Fix:

// UserRepository.java
public interface UserRepository extends JpaRepository<UserEntity, UUID> {
    // findById(UUID) is now inherited — remove the explicit declaration
    ...
}

// RefreshTokenRepository.java
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, UUID> { ... }

6. UserDetailsImpl.getAuthorities() always returns empty list

File: UserDetailsImpl.java — Line 38

The user's Role is never converted to GrantedAuthority. Every user has no authorities, so @PreAuthorize("hasRole('TEACHER')") and similar annotations will never work. The authorities claim in the JWT is also always empty.

Fix:

// UserDetailsImpl.java
private final Role role;

public static UserDetailsImpl build(UserEntity user) {
    return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash(), user.getRole());
}

`@Override`
public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}

Remove the commented-out code from UserEntity — it was the right idea but in the wrong class.


7. Refresh token uses access token TTL — token will expire too quickly

File: AuthService.java — Lines 52, 85

refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())) — the refresh token expires at the same time as the access token. Refresh tokens should live much longer (7–30 days vs. ~15 minutes for access tokens).

Fix: Add a separate property to JwtService:

# application.yaml
security:
  jwt:
    secret-key: ...
    expiration-time: 900000       # 15 minutes (access token)
    refresh-expiration-time: 604800000  # 7 days (refresh token)
`@Getter`
`@Value`("${security.jwt.refresh-expiration-time}")
private long refreshExpiration;

8. Race condition in register — non-atomic check-then-save

File: AuthService.java — Lines 41–43

if (repository.existsByEmail(...)) followed by repository.save() is not atomic. Two concurrent registrations with the same email can both pass the check and both save.

Fix: Remove the pre-check and catch the DB unique constraint violation:

try {
    repository.save(userEntity);
} catch (DataIntegrityViolationException e) {
    throw new IllegalStateException("Email already in use");
}

9. UserDetailsImpl.getUsername() returns display name, not login identifier

File: UserDetailsImpl.java — Line 43

Spring Security's UserDetails.getUsername() contract expects it to return the principal identifier (the login). Here, email is the login but getUsername() returns the display username. This violates Liskov Substitution (the contract of UserDetails) and causes confusion in logging and security auditing.

Fix:

`@Override`
public String getUsername() {
    return email; // email is the login identifier
}
// Add a separate getDisplayName() if you need the username
public String getDisplayName() {
    return username;
}

🟡 P3 — Code Quality / SOLID / Boilerplate


10. No validation annotations on request DTOs

File: LoginRequest.java
File: RegisterRequest.java
File: RefreshTokenRequest.java

@Valid in the controller does nothing without constraint annotations on the record components.

Fix:

public record LoginRequest(
    `@NotBlank` `@Email` String email,
    `@NotBlank` `@Size`(min = 8, max = 72) String password) {}

public record RegisterRequest(
    `@NotBlank` `@Size`(max = 50) String username,
    `@NotBlank` `@Email` String email,
    `@NotBlank` `@Size`(min = 8, max = 72) String password) {}

public record RefreshTokenRequest(
    `@NotBlank` `@JsonProperty`("refresh_token") String refreshToken) {}

11. getNewToken returns HTTP 201 — should be 200

File: AuthController.java — Line 38

Token refresh is not resource creation. Return 200 OK:

return ResponseEntity.ok(authService.getNewToken(refreshTokenRequest));

12. Duplicate refresh token creation logic (Boilerplate)

File: AuthService.java — Lines 47–55, 80–88, and analogous block in getNewToken

The same 5-line block for creating and saving a RefreshTokenEntity appears three times. Extract it:

private RefreshTokenEntity createRefreshToken(UserEntity user, String rawToken) {
    RefreshTokenEntity entity = new RefreshTokenEntity();
    entity.setToken(rawToken);
    entity.setUser(user);
    entity.setExpiresAt(Instant.now().plusMillis(jwtService.getRefreshExpiration()));
    return refreshTokenRepository.save(entity);
}

13. CustomUserDetailsService is in the wrong package

File: CustomUserDetailsService.java

It is a service, not a configuration class. It belongs in security or service package. Placing it in config violates SRP — the config package should contain only Spring configuration beans.

Fix: Move to com.smartjam.smartjamapi.security alongside JwtService and JwtAuthenticationFilter.


14. ApplicationConfig.userDetailsService() is unnecessary (Boilerplate)

File: ApplicationConfig.java — Lines 16–18

CustomUserDetailsService already implements UserDetailsService. Wrapping it in a @Bean lambda is redundant — Spring will autowire it directly.

Fix: Remove userDetailsService() bean method. Anywhere a UserDetailsService is injected, CustomUserDetailsService will be resolved automatically.


15. Unsafe cast in JwtService.extractAllClaims()

File: JwtService.java — Lines 27, 67

getSigningKey() returns Key, then is unsafely cast to SecretKey. Change the return type:

private SecretKey getSigningKey() {
    byte[] keyBytes = Decoders.BASE64.decode(secretKey);
    return Keys.hmacShaKeyFor(keyBytes); // already returns SecretKey
}

16. UploadService creates new AWS clients on every request (Performance / DIP)

File: UploadService.java — Line 43

generateUploadUrl() creates a new S3Presigner inside try-with-resources on every call. S3Presigner is designed to be long-lived. Extract it as a @Bean in a config class.


17. AuthService uses @AllArgsConstructor (Boilerplate)

File: AuthService.java — Line 26

Prefer @RequiredArgsConstructor (the Spring convention) since all fields are already final. @AllArgsConstructor on a Spring bean is a code smell.


18. AvailabilityStatus misused in AuthResponse

File: AuthResponse.java — Line 8
File: AvailabilityStatus.java

AvailabilityStatus.AVAILABLE/BANNED/PENDING/UNAVAILABLE looks like a user/resource availability status, not an auth response status. All three auth responses hardcode AVAILABLE. Either: (a) remove the status field from AuthResponse (HTTP status code is sufficient), or (b) create a dedicated AuthStatus enum.


19. Dead commented-out code in UserEntity

File: UserEntity.java — Lines 51–58

Remove the commented-out getAuthorities() and getPassword() overrides — UserEntity should not implement UserDetails (that's UserDetailsImpl's responsibility). This is a good separation of concerns — just delete the dead code.


20. UploadController missing @RequestMapping prefix

File: UploadController.java — Line 12

No base path → /upload-url is exposed with no URL namespace. Consider adding @RequestMapping("/api/upload").


21. Typo: presignedUtl in UploadService

File: UploadService.java — Line 62

presignedUtlpresignedUrl.


22. build.gradle has two dependencies {} blocks at the root level

File: build.gradle

There are two separate dependencies {} blocks in the root build.gradle, one seemingly pre-existing and one added by the PR. This results in Gradle parsing two dependency configs. Root project dependencies are also unnecessary if smartjam-api/build.gradle already declares them correctly. Consolidate or remove the root-level blocks.


📐 SOLID Compliance Summary

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

🟠 P2 — Important Correctness Issues

🟡 P3 — Code Quality


✏️ 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.

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== UserEntity ==="
find . -path "*/entity/UserEntity.java" | xargs cat

echo "=== RefreshTokenEntity ==="
find . -path "*/entity/RefreshTokenEntity.java" | xargs cat

echo "=== UserRepository ==="
find . -path "*/repository/UserRepository.java" | xargs cat

echo "=== RefreshTokenRepository ==="
find . -path "*/repository/RefreshTokenRepository.java" | xargs cat

echo "=== AuthService ==="
find . -path "*/service/AuthService.java" | xargs cat

echo "=== JwtService ==="
find . -path "*/security/JwtService.java" | xargs cat

echo "=== JwtAuthenticationFilter ==="
find . -path "*/security/JwtAuthenticationFilter.java" | xargs cat

echo "=== UserDetailsImpl ==="
find . -path "*/security/UserDetailsImpl.java" | xargs cat

echo "=== SecurityConfig ==="
find . -path "*/config/SecurityConfig.java" | xargs cat

echo "=== ApplicationConfig ==="
find . -path "*/config/ApplicationConfig.java" | xargs cat

echo "=== CustomUserDetailsService ==="
find . -path "*/config/CustomUserDetailsService.java" | xargs cat

echo "=== AuthController ==="
find . -path "*/controller/AuthController.java" | xargs cat

echo "=== MainController ==="
find . -path "*/controller/MainController.java" | xargs cat

echo "=== UploadController ==="
find . -path "*/controller/UploadController.java" | xargs cat

echo "=== GlobalExceptionHandler ==="
find . -path "*/exception/GlobalExceptionHandler.java" | xargs cat

echo "=== DTOs ==="
find . -path "*/dto/*.java" | xargs cat

echo "=== Enums ==="
find . -path "*/enums/*.java" | xargs cat

echo "=== UploadService ==="
find . -path "*/service/UploadService.java" | xargs cat

echo "=== build.gradle ==="
find . -name "build.gradle" | xargs cat

echo "=== application.properties or application.yml ==="
find . -name "application*.properties" -o -name "application*.yml" | xargs cat 2>/dev/null

echo "=== SmartjamApiApplication ==="
find . -name "SmartjamApiApplication.java" | xargs cat

Length of output: 34080


@Satlykovs — here is the complete, prioritized rescan report with links to every affected file.


🔍 Full PR Rescan — Complete Change Checklist


🔴 P0 — Critical Bugs / Will Break at Runtime


1. 🔗 UserRepository.java & RefreshTokenRepository.javaID type mismatch (DB inconsistency)

Both repositories declare Long as the ID type while the entities use UUID. Spring Data will throw a ClassCastException at runtime on any findById, deleteById, or existsById call.

// ❌ Now
public interface UserRepository extends JpaRepository<UserEntity, Long>
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long>

// ✅ Fix
public interface UserRepository extends JpaRepository<UserEntity, UUID>
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, UUID>

Also: UserRepository.findById(UUID id) is a redundant custom method that shadows JpaRepository.findById() — remove it after fixing the generic type above.


2. 🔗 build.gradleTwo dependencies {} blocks concatenated (malformed Gradle)

The file ends with }dependencies { — two blocks are stuck together. Gradle will likely fail to parse or silently ignore one of them. Merge them into a single dependencies {} block.


3. Missing application.propertiesApp will not start

JwtService reads:

security.jwt.secret-key=...
security.jwt.expiration-time=...

No application.properties / application.yml was found in the repo. The app will throw IllegalArgumentException: Could not resolve placeholder at startup. Create the file:

security.jwt.secret-key=<base64-encoded-256-bit-secret>
security.jwt.expiration-time=3600000

🔴 P1 — Security Vulnerabilities


4. 🔗 AuthService.java L28–33Account enumeration

Different error messages for "user not found" vs "wrong password" let an attacker enumerate valid emails.

// ❌ Now
.orElseThrow(() -> new NoSuchElementException("Login not found, try register, please"));
// ...
throw new IllegalStateException("Invalid password");

// ✅ Fix — same message for both
.orElseThrow(() -> new IllegalStateException("Invalid credentials"));
// ...
throw new IllegalStateException("Invalid credentials");

5. 🔗 GlobalExceptionHandler.javaNoSuchElementException not mapped → 500 on login failure

AuthService throws NoSuchElementException for "user not found" and "token not found", but GlobalExceptionHandler has no handler for it. It falls through to the generic 500 handler — every failed login returns HTTP 500.

// ✅ Fix — add handler
`@ExceptionHandler`(NoSuchElementException.class)
public ResponseEntity<ErrorResponseDto> handleNotFound(NoSuchElementException e) {
    log.warn("Not found: {}", e.getMessage());
    return buildResponse(HttpStatus.NOT_FOUND, ErrorCode.NOT_FOUND, "Resource not found");
}

Tip: After unifying login errors (P1 item above), the two cases can both use IllegalStateException → 400/401.


6. 🔗 JwtAuthenticationFilter.java L35–38Malformed JWT causes unhandled exception → 500

jwtService.extractUsername(jwt) (which calls Jwts.parser()...parseSignedClaims()) throws JwtException on any invalid token. This is not caught, resulting in a 500 response instead of a 401.

// ✅ Fix — wrap in try-catch
try {
    final String email = jwtService.extractUsername(jwt);
    // ... rest of auth logic
} catch (JwtException | IllegalArgumentException e) {
    log.warn("Invalid JWT token: {}", e.getMessage());
    filterChain.doFilter(request, response);
    return;
}

7. 🔗 AuthService.java L79–86Refresh token rotation without invalidation (token replay)

getNewToken() saves a new refresh token but never deletes the old one. The consumed token remains valid in the DB, allowing replay attacks.

// ✅ Fix — delete old token before saving new
refreshTokenRepository.delete(refreshToken);  // add this line
// then save new token...

8. 🔗 UploadService.java L31, L45Hardcoded credentials

// ❌ Now
AwsBasicCredentials.create("minioadmin", "minioadmin")
URI.create("http://localhost:9000")

// ✅ Fix — inject from config
`@Value`("${minio.endpoint}") private String endpoint;
`@Value`("${minio.access-key}") private String accessKey;
`@Value`("${minio.secret-key}") private String secretKey;

🟠 P2 — Architecture / SOLID Violations


9. 🔗 UserDetailsImpl.java L37LSP violation: getAuthorities() returns empty collection

@EnableMethodSecurity is active in SecurityConfig, but getAuthorities() always returns List.of(). Role-based access control (@PreAuthorize("hasRole('TEACHER')")) will silently deny everything. The role exists in UserEntity but is never mapped.

// ✅ Fix — map the role to a GrantedAuthority
`@Override`
public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
// Add `role` field to UserDetailsImpl and populate it in build()
public static UserDetailsImpl build(UserEntity user) {
    return new UserDetailsImpl(user.getId(), user.getUsername(),
        user.getEmail(), user.getPasswordHash(), user.getRole());
}

10. 🔗 AuthService.java L53SRP violation: token persistence mixed into AuthService + register() not @Transactional

AuthService handles authentication logic and manages RefreshTokenEntity persistence. If the refreshTokenRepository.save() fails in register(), the user is already saved but has no token — a partial state.

  • Extract token management into a dedicated RefreshTokenService.
  • Add @Transactional to register().
// ✅ Fix
`@Transactional`  // add this
public AuthResponse register(RegisterRequest request) { ... }

11. 🔗 GlobalExceptionHandler.java L38IllegalArgumentException → 401 is overly broad

IllegalArgumentException is a JDK base class thrown by many framework internals (e.g., bad @Value binding). Mapping it to 401 will produce misleading responses for completely unrelated errors. Use a custom exception class instead:

// ✅ Create custom exception
public class AuthenticationException extends RuntimeException { ... }

// In GlobalExceptionHandler
`@ExceptionHandler`(AuthenticationException.class)
public ResponseEntity<ErrorResponseDto> handleUnauthenticated(AuthenticationException e) { ... }

12. 🔗 ApplicationConfig.java L15–17DRY violation: redundant UserDetailsService bean

CustomUserDetailsService already implements UserDetailsService. The lambda wrapper customUserDetailsService::loadUserByUsername is redundant and creates two beans for the same interface.

// ❌ Remove this bean — not needed
`@Bean`
public UserDetailsService userDetailsService() {
    return customUserDetailsService::loadUserByUsername;
}

Simply annotate CustomUserDetailsService with @Service (already done) and Spring will wire it automatically.


13. 🔗 CustomUserDetailsService.javaSRP violation: service class in config package

CustomUserDetailsService is a service, not configuration. Move it to the service package.


🟡 P3 — Code Quality / Boilerplate


14. 🔗 LoginRequest.java, RegisterRequest.java, RefreshTokenRequest.javaMissing validation annotations

@Valid in controllers does nothing without constraints on the record components:

// ✅ LoginRequest
public record LoginRequest(
    `@NotBlank` `@Email` String email,
    `@NotBlank` `@Size`(min = 8, max = 72) String password) {}

// ✅ RegisterRequest
public record RegisterRequest(
    `@NotBlank` `@Size`(max = 50) String username,
    `@NotBlank` `@Email` String email,
    `@NotBlank` `@Size`(min = 8, max = 72) String password) {}

// ✅ RefreshTokenRequest
public record RefreshTokenRequest(
    `@NotBlank` `@JsonProperty`("refresh_token") String refreshToken) {}

15. 🔗 AuthController.java L33, L39Wrong HTTP status for /refresh + magic number 201

  • /refresh returns 201 Created — it should return 200 OK (no new resource is created).
  • Use HttpStatus.CREATED constant instead of the magic number 201.
// ✅ register — use constant
return ResponseEntity.status(HttpStatus.CREATED).body(authService.register(request));

// ✅ refresh — should be 200, not 201
return ResponseEntity.ok(authService.getNewToken(refreshTokenRequest));

16. 🔗 AuthService.java L44–49Boilerplate setter chain → use @Builder

// ❌ Now — 4 lines of setters
UserEntity userEntity = new UserEntity();
userEntity.setUsername(request.username());
userEntity.setEmail(request.email());
userEntity.setPasswordHash(passwordEncoder.encode(request.password()));
userEntity.setRole(Role.STUDENT);

// ✅ Fix — add `@Builder` to UserEntity and use:
UserEntity userEntity = UserEntity.builder()
        .username(request.username())
        .email(request.email())
        .passwordHash(passwordEncoder.encode(request.password()))
        .role(Role.STUDENT)
        .build();

Add @Builder to UserEntity. Do the same for the 3-line RefreshTokenEntity constructor blocks (appears 3 times in AuthService):

RefreshTokenEntity token = RefreshTokenEntity.builder()
        .token(refreshToken)
        .user(userEntity)
        .expiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration()))
        .build();

17. 🔗 AuthService.java L22@AllArgsConstructor instead of @RequiredArgsConstructor

All injected fields are final, so @RequiredArgsConstructor is the correct Lombok annotation.

// ❌ `@AllArgsConstructor`
// ✅ `@RequiredArgsConstructor`

18. 🔗 UploadController.javaMissing @RequestMapping, wrong Lombok annotation

The upload endpoint /upload-url has no base path and is exposed without authentication mapping in SecurityConfig. Add a @RequestMapping("/api/upload") and switch @AllArgsConstructor@RequiredArgsConstructor.


19. 🔗 UserRepository.java L10Misleading parameter name + unused methods

// ❌ parameter named `login` in findByEmail
Optional<UserEntity> findByEmail(String login);
// ✅
Optional<UserEntity> findByEmail(String email);

existsByUsername() and findUserEntitiesByUsername() are never called in the current codebase — remove or keep only if planned for near future.


20. 🔗 UserEntity.java L52–59Commented-out dead code

Remove the commented-out getAuthorities() and getPassword() block entirely. Once roles are mapped in UserDetailsImpl (item 9 above), these are not needed on the entity.


21. 🔗 UploadService.java L54Typo in variable name

// ❌
URL presignedUtl = presignedGetObjectRequest.url();
// ✅
URL presignedUrl = presignedGetObjectRequest.url();

📐 SOLID Summary Table

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.properties with 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (24)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java (1)

1-3: ⚠️ Potential issue | 🟠 Major

Add Bean Validation constraints to registration fields.

This record currently accepts blank usernames, invalid emails, and short/blank passwords. Add @NotBlank to username, @NotBlank @Email`` to email, and `@NotBlank `@Size`(min = 8)` to `password`.

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, 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 | 🟠 Major

Use an auth-specific status contract here.

AvailabilityStatus is an availability-domain enum, but the auth flow currently uses AVAILABLE to 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 | 🟠 Major

Reject 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 @NotBlank to 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 | 🟠 Major

Add Bean Validation constraints to the login DTO.

As written, this record accepts blank or malformed credentials. Add @NotBlank @Email`` to email and `@NotBlank `@Size`(min = 8)` to `password` so invalid payloads are rejected before auth logic runs.

Suggested 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 | 🟠 Major

Don'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 | 🔵 Trivial

Reuse SecureRandom instance instead of creating a new one per call.

Creating a new SecureRandom instance 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 | 🔵 Trivial

Cache the signing key to avoid repeated decoding.

getSigningKey() decodes the base64 secret and creates a new Key object 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 | 🔴 Critical

Authorities 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. The build() method must pass the user's role, and getAuthorities() 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 | 🔵 Trivial

Align parameter name with what it represents.

The parameter login in findByEmail(String login) is misleading. Use email for clarity and consistency with the method name.

Suggested 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 | 🟡 Minor

Fix typo: presignedUtl should be presignedUrl.

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 | 🔴 Critical

Do 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 | 🟠 Major

Validate/sanitize fileName before 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 | 🟠 Major

Remove the redundant UserDetailsService bean to avoid bean ambiguity.

CustomUserDetailsService is already a bean; exposing another UserDetailsService here 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 | 🟡 Minor

Use proper HTTP statuses: keep 201 Created for register, return 200 OK for refresh.

Line 32 should use HttpStatus.CREATED instead of literal 201, and Line 38 should return ResponseEntity.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 /refresh should return 200 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 | 🟡 Minor

Fix 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 | 🔵 Trivial

Use log.debug instead of log.info for 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 | 🟠 Major

Unhandled JWT exceptions will propagate as 500 errors.

jwtService.extractUsername(jwt) and jwtService.isTokenValid() call into jjwt's parser, which throws ExpiredJwtException, 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 AuthenticationEntryPoint for 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

@Slf4j added but no logging after successful operations.

The class now has @Slf4j but 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 | 🔵 Trivial

Use builder pattern for entity creation.

Manual setter chains are verbose and error-prone. Use Lombok's @Builder on 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 | 🟠 Major

Refresh 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 like security.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 @Transactional and has a race condition.

  1. The method performs multiple DB operations (user save + refresh token save) without @Transactional, risking inconsistent state on failure.
  2. 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 | 🟠 Major

Account 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 | 🟠 Major

Old refresh token is not invalidated after use.

When issuing a new refresh token in getNewToken(), the old RefreshTokenEntity remains 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 | 🔵 Trivial

Fix method naming: use "handle" instead of "handler".

Method names handlerGenericException, handlerEntityNotFound, and handlerBadRequest should 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

📥 Commits

Reviewing files that changed from the base of the PR and between 32fba48 and e1ca78a.

📒 Files selected for processing (28)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
  • backend/smartjam-api/src/main/resources/application.yaml

Comment on lines +50 to +59
// @Override
// public @NonNull Collection<? extends GrantedAuthority> getAuthorities() {
// return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
// }
//
// @Override
// public @NonNull String getPassword() {
// return passwordHash;
// }
}
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
// @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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (28)
backend/smartjam-api/build.gradle (1)

6-8: ⚠️ Potential issue | 🟠 Major

Upgrade 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 | 🔵 Trivial

Delete the commented-out UserDetails methods.

This block suggests UserEntity still owns the security contract, but the active implementation lives in UserDetailsImpl. 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 | 🟠 Major

Put Bean Validation on the record components.

As written, controller-level @Valid still won't reject blank usernames, malformed emails, or empty passwords. Those checks need to live on the RegisterRequest components 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 | 🟠 Major

Generate the object key server-side instead of trusting fileName.

Line 59 uses the request's fileName as 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 | 🔵 Trivial

Inject a shared S3Presigner instead 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 | 🔴 Critical

Move the MinIO endpoint and credentials out of source control.

S3_ENDPOINT and both AwsBasicCredentials.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 | 🟠 Major

Add @NotBlank to refreshToken.

backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java, Lines 35-39 already use @Valid, but this DTO has no constraint, so null/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 | 🟠 Major

Do 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 write newRefreshToken straight into this field and look it up with findByToken(...). 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 in RefreshTokenEntity, 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 | 🔴 Critical

Fix JpaRepository ID type and remove shadow findById (Lines 9-14).

UserEntity is UUID-keyed, so repository generic must be UUID. Current findById(UUID) only masks the mismatch for one method; inherited methods still use Long.

🐛 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.java

Based on learnings: In SmartJam, UserRepository must extend JpaRepository<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 | 🔵 Trivial

Rename query signatures for clarity.

findByEmail(String login) mixes terms, and findUserEntitiesByUsername is oddly pluralized for a single-entity Optional. Prefer findByEmail(String email) and 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 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 | 🔵 Trivial

Replace AvailabilityStatus with auth-domain status.

For auth endpoints, AvailabilityStatus is 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 | 🔴 Critical

Fix repository ID generic to UUID (Line 8).

RefreshTokenRepository uses Long as 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.java

Based 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 | 🟠 Major

Remove duplicate UserDetailsService bean (Lines 16-19).

CustomUserDetailsService is already a @Service implementing UserDetailsService. 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 | 🔵 Trivial

Use an auth-specific status enum for auth responses.

AvailabilityStatus reads like profile/resource availability, not authentication outcome. Consider AuthStatus/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 | 🟠 Major

Map 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 map Role to GrantedAuthority.

🤖 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 | 🟠 Major

Add bean-validation constraints to LoginRequest.

@Valid in 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 | 🟡 Minor

Use proper HTTP status semantics and avoid numeric status literals.
/register should use HttpStatus.CREATED instead of 201, and /refresh should return 200 OK (ResponseEntity.ok(...)), not 201 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 /refresh should 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 | 🔵 Trivial

Cache signing key and reuse SecureRandom instance.
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 | 🟡 Minor

Fix 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 | 🟠 Major

Avoid logging raw principal identifiers at info level.
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 | 🟠 Major

401/400 mapping is inverted for current auth exceptions.
IllegalStateException is used for auth failures in AuthService, but here it is mapped to 400. It should be mapped to 401, with IllegalArgumentException moved 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 IllegalStateException and 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 | 🟠 Major

Make 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 | 🔴 Critical

Persist 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 | 🟠 Major

Use 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 | 🔴 Critical

Unify 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 | 🟠 Major

Replace NoSuchElementException in auth flow with explicitly mapped auth exceptions.

Using NoSuchElementException here 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 | 🔴 Critical

Invalidate 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 | 🟠 Major

Handle 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

📥 Commits

Reviewing files that changed from the base of the PR and between 32fba48 and e1ca78a.

📒 Files selected for processing (28)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
  • backend/smartjam-api/src/main/resources/application.yaml

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

♻️ Duplicate comments (13)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java (1)

11-11: 🧹 Nitpick | 🔵 Trivial

Rename 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. Use String email here.

🤖 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 | 🔴 Critical

Remove 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 | 🟠 Major

Don’t use the client-supplied fileName as 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 | 🔵 Trivial

Delete the commented-out UserDetails methods.

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 | 🟠 Major

Add Bean Validation annotations to the record components.

@Valid on the controller does nothing until email, nickname, and password declare constraints here, so blank or malformed registration data still reaches AuthService.register(...). At minimum, this DTO should carry @Email/@NotBlank and 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 | 🟡 Minor

Typo: "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 | 🔵 Trivial

Remove 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 | 🟡 Minor

Fix Spotless formatting violations.

The pipeline reports spotlessJavaCheck failed due to format violations (import order/spacing). Run ./gradlew spotlessApply before 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 | 🔵 Trivial

Use HttpStatus.CREATED instead of the numeric literal here.

201 is 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 | 🟠 Major

Return one generic login failure for both branches.

NoSuchElementException("Login not found...") and IllegalStateException("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 | 🟠 Major

Handle token parsing and user lookup failures inside the filter.

jwtService.extractUsername(jwt) and customUserDetailsService.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 | 🟠 Major

Map the user's role into getAuthorities() instead of returning List.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

📥 Commits

Reviewing files that changed from the base of the PR and between e1ca78a and a8ff2bd.

📒 Files selected for processing (25)
  • backend/smartjam-api/build.gradle
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java
  • backend/smartjam-api/src/main/resources/application.yaml

Comment on lines +18 to +22
@Override
public UserDetailsImpl loadUserByUsername(@NotBlank String email) throws UsernameNotFoundException {
UserEntity user = userRepository
.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Invalid credentials"));
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines +5 to +8
public class SecurityException extends AuthenticationException {
public SecurityException(String message) {
super(message);
}
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines +6 to +8
@Mapper(componentModel = "spring")
public interface UserMapper {
UserEntity toEntity(RegisterRequest request);
Copy link

Choose a reason for hiding this comment

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

🧹 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).

Comment on lines +7 to +10
import com.smartjam.smartjamapi.exception.TokenExpiredException;
import com.smartjam.smartjamapi.exception.TokenNotFoundException;
import com.smartjam.smartjamapi.mapper.UserMapper;
import com.smartjam.smartjamapi.security.RefreshTokenService;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
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.java

Repository: 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.

Comment on lines +117 to +141
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);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +59 to +70
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);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (10)
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java (2)

26-26: ⚠️ Potential issue | 🟡 Minor

Fix 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 | 🟠 Major

Do not log raw principal identifiers at INFO level.

Line 17 and Line 19 log principal.getName() directly at info, 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 | 🟠 Major

Static fields create lifecycle and testability issues.

Using static fields for S3Client and S3Presigner circumvents 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 @PreDestroy hook to call .close())

Declare these as instance fields and configure them as proper @Beans in a @Configuration class, 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 | 🔵 Trivial

Rename parameter login to email for consistency.

The method is findByEmail but the parameter is named login. 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 | 🟡 Minor

Replace 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 as HttpStatus.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

SecurityException here likely resolves to the JDK type, not your auth exception.

Without importing com.smartjam.smartjamapi.exception.SecurityException, Line 121 resolves to java.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 | 🔴 Critical

Unify login failure path to prevent account enumeration.

Line 46 and Line 48 expose different failure reasons (user missing vs wrong 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 + save is 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 | 🔴 Critical

Reject all non-ACTIVE refresh tokens and claim token state atomically.

Current logic only blocks USED. A REVOKED token 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

@ExceptionHandler signature is invalid for one declared exception type.

Line 66 includes IllegalStateException, but Line 67 accepts only AuthenticationException. Spring cannot bind IllegalStateException to 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

📥 Commits

Reviewing files that changed from the base of the PR and between a8ff2bd and 535bcf8.

📒 Files selected for processing (14)
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java

}

@GetMapping("/hello")
// @PreAuthorize()
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
// @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.

Comment on lines +20 to +23
// TODO: Это базовый шаблон для обработки ошибок
// TODO: Возможно, некоторые исключения ловятся неправильно
// TODO: Нужно будет потом уточнить маппинг (какое исключение -> какой статус)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +18 to +21
@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);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +23 to +34
@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);
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines +60 to +66
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith((SecretKey) getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for JwtException handling in JwtAuthenticationFilter and GlobalExceptionHandler
rg -n "JwtException|ExpiredJwtException|MalformedJwtException" --type java -C 3

Repository: Satlykovs/SmartJam

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Find JwtAuthenticationFilter
fd -e java | xargs grep -l "JwtAuthenticationFilter\|class.*Filter" | head -20

Repository: 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 2

Repository: Satlykovs/SmartJam

Length of output: 5765


🏁 Script executed:

#!/bin/bash
# Find where parseSignedClaims or extractAllClaims is called
rg -n "extractAllClaims|parseSignedClaims" --type java -C 3

Repository: 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 -n

Repository: 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.java

Repository: 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 2

Repository: 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.

Comment on lines +27 to +31
public String generateRefreshToken() {
byte[] randomBytes = new byte[64];
new SecureRandom().nextBytes(randomBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
}
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines +33 to +40
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);
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 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);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

abebe1 added 2 commits March 21, 2026 01:07
…228/feature/auth

# Conflicts:
#	backend/smartjam-api/build.gradle
#	backend/smartjam-api/src/main/resources/application.yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants