Conversation
Repository's and custom DB request's
Note create post-mapping
User service DB update User controller + user postmapping
User, Note validation
Notes delete
|
Warning Rate limit exceeded@Kirill9m has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 37 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (7)
WalkthroughReplaces a simple Java app with a Spring Boot auth service: adds Spring Boot parent/pom changes, Docker Compose for MySQL, application properties and dev profile, JPA entities/repositories, controllers, services, JWT/BCrypt utilities, security filter/config, global exception handling, and integration tests; removes original App and legacy unit tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController
participant UserService
participant UserRepository
participant TokenRepository
participant JwtUtil
participant BCryptUtil
Note over Client,UserService: User registration
Client->>UserController: POST /user/register {name,email,password}
UserController->>UserService: registerUser(User)
UserService->>UserRepository: existsByEmail(email)
alt email exists
UserService-->>UserController: throw ConflictException
UserController-->>Client: 409 Conflict
else create user
UserService->>BCryptUtil: hashPassword(password)
BCryptUtil-->>UserService: hashed
UserService->>UserRepository: save(UserEntity)
UserService->>JwtUtil: generateToken(email, accessExp)
JwtUtil-->>UserService: accessToken
UserService->>JwtUtil: generateToken(email, refreshExp)
JwtUtil-->>UserService: refreshToken
UserService->>TokenRepository: save(TokenEntity)
UserService-->>UserController: UserNew(...)
UserController-->>Client: 201 Created
end
sequenceDiagram
participant Client
participant SecurityChain
participant ApiKeyFilter
participant JwtUtil
participant UserRepository
participant Controller
Note over Client,Controller: Authenticated API request
Client->>SecurityChain: GET /api/notes/user (Authorization: Bearer token)
SecurityChain->>ApiKeyFilter: doFilterInternal()
ApiKeyFilter->>JwtUtil: validateJwtToken(token)
alt invalid token
JwtUtil-->>ApiKeyFilter: false
ApiKeyFilter-->>Client: 401 Unauthorized
else valid token
JwtUtil-->>ApiKeyFilter: true
ApiKeyFilter->>JwtUtil: getUsernameFromToken(token)
JwtUtil-->>ApiKeyFilter: email
ApiKeyFilter->>UserRepository: findByEmail(email)
alt user not found
UserRepository-->>ApiKeyFilter: empty
ApiKeyFilter-->>Client: 401 Unauthorized
else user found
ApiKeyFilter-->>SecurityChain: set Authentication (ApiPrincipal)
SecurityChain->>Controller: forward request with auth
Controller-->>Client: 200 OK
end
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (19)
src/main/java/org/example/dto/user/UserApi.java (1)
5-7: Consider more descriptive naming for future integration.This record is not currently used in any endpoint, but the naming
UserApiis generic and doesn't clearly convey its purpose (token usage tracking based on the fields). If/when this record is integrated into API responses, consider a more descriptive name likeTokenUsageInfo,UserTokenResponse, orApiTokenDetails.Additionally, note that the
tokenfield contains sensitive data; if this record is ever returned in responses or logged, ensure the token is excluded or redacted to prevent accidental exposure.src/main/java/org/example/dto/user/User.java (1)
6-8: Remove redundant @NotNull annotations.The
@NotBlankannotation already implies@NotNullfor String fields, making the explicit@NotNullannotation redundant.Apply this diff to simplify the validation:
-public record User(@NotNull @NotBlank String name, - @NotNull @NotBlank String password, - @NotNull @NotBlank String email) { +public record User(@NotBlank String name, + @NotBlank String password, + @NotBlank String email) {src/main/java/org/example/dto/user/UserCheck.java (1)
6-6: Remove redundant @NotNull annotations.Same as in
User.java, the@NotBlankannotation already implies@NotNull, making the explicit@NotNullredundant.Apply this diff:
-public record UserCheck(@NotNull @NotBlank String password, @NotNull @NotBlank String email) { +public record UserCheck(@NotBlank String password, @NotBlank String email) {src/main/java/org/example/dto/token/TokenRequest.java (1)
3-4: Consider adding validation annotations for required fields.The record structure is clean. However, for security-sensitive operations like login and token refresh, consider adding
@NotBlankannotations to ensure these required fields are validated at the API boundary.Example:
-public record TokenRequest(String token, String refreshToken) { +public record TokenRequest(@NotBlank String token, @NotBlank String refreshToken) {src/main/java/org/example/Exercise2025Application.java (1)
5-5: Remove unused import.The
SecurityAutoConfigurationimport is not used in this file.Apply this diff:
-import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; - @SpringBootApplicationsrc/main/java/org/example/utils/BCryptUtil.java (2)
6-9: Add input validation to prevent null pointer exceptions.Both methods should validate that input strings are not null before processing to avoid runtime NPEs.
Apply this diff:
public static String hashPassword(String password) { + if (password == null) { + throw new IllegalArgumentException("Password cannot be null"); + } String salt = BCrypt.gensalt(10); return BCrypt.hashpw(password, salt); }
11-13: Add input validation to prevent null pointer exceptions.Validate inputs to avoid NPEs when either parameter is null.
Apply this diff:
public static boolean checkPassword(String plainPassword, String hashedPassword) { + if (plainPassword == null || hashedPassword == null) { + return false; + } return BCrypt.checkpw(plainPassword, hashedPassword); }src/main/java/org/example/exceptionHandler/GlobalExceptionHandler.java (1)
41-48: Use ErrorResponse DTO for consistency.This handler returns an inline map while other handlers use the
ErrorResponseDTO. Using the same DTO across all handlers improves consistency and type safety.Apply this diff:
@ExceptionHandler(ConflictException.class) -public ResponseEntity<?> handleExists(ConflictException ex) { - return ResponseEntity.status(HttpStatus.CONFLICT) - .body(Map.of( - "status", 409, - "message", ex.getMessage() - )); +public ResponseEntity<ErrorResponse> handleExists(ConflictException ex) { + ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), HttpStatus.CONFLICT.value()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse); }src/main/resources/application.properties (2)
5-5: Make database URL configurable for different environments.The database URL is hardcoded to
localhost:3306, which makes it difficult to deploy to different environments (staging, production, containers).Apply this diff:
-spring.datasource.url=jdbc:mysql://localhost:3306/auth +spring.datasource.url=${DATABASE_URL:jdbc:mysql://localhost:3306/auth}
11-11: Make SQL logging configurable for different environments.SQL query logging enabled in production can cause performance overhead, log bloat, and potential information disclosure.
Apply this diff:
-spring.jpa.show-sql=true +spring.jpa.show-sql=${SQL_LOGGING:false}src/main/java/org/example/service/TokenService.java (2)
32-34: Consider using SecurityException for invalid tokens.Throwing
EntityNotFoundExceptionfor an invalid token is semantically imprecise. An invalid or expired token is an authentication/authorization failure, not a missing resource.Apply this diff:
if (!jwtUtil.validateJwtToken(refreshToken)) { - throw new EntityNotFoundException("Invalid refresh token"); + throw new SecurityException("Invalid or expired refresh token"); }
36-36: Consider making token expiry configurable.The access token expiry (5 minutes) is hardcoded, making it difficult to adjust for different environments or security requirements without code changes.
Consider moving token expiry durations to
application.properties:jwt.access-token-expiry=300000 jwt.refresh-token-expiry=604800000Then inject these values using
@Valueannotations.src/main/java/org/example/service/NoteService.java (1)
47-52: Consider moving deleted-filtering into a repository query
user.getNotes().stream().filter(note -> note.getDeletedAt() == null)loads all notes into memory before filtering. For users with many notes, a repository method likefindAllByUserAndDeletedAtIsNull(or equivalent) would be more scalable and explicit.src/main/java/org/example/filter/ApiKeyFilter.java (1)
30-71: JWT filter behavior is correct; a few optional hardening tweaksThe filter correctly:
- Skips non-
/api/URIs,- Validates the Bearer token,
- Loads the user, and
- Populates the
SecurityContextwith anApiPrincipal.Optional improvements:
- Consider using
@AuthenticationPrincipal ApiPrincipaldownstream to avoid manual casting.- If you ever add
/api(without trailing slash) endpoints,!path.startsWith("/api/")will bypass the filter for them; you might want a more robust path check.- If clients expect JSON error bodies, you could delegate 401 responses to a centralized handler instead of
sendError.src/main/java/org/example/controller/NoteController.java (1)
25-54: Endpoints are wired correctly; consider using@AuthenticationPrincipaland renaming the getterThe note endpoints correctly delegate to
NoteServiceusing the authenticated user id/email. Two small polish items:
- Instead of accepting
Authenticationand casting, you can injectApiPrincipaldirectly with@AuthenticationPrincipal ApiPrincipal principalto avoid potentialClassCastExceptionif other auth types are ever introduced.getUserByIdactually returns notes for the current user; a name likegetUserNoteswould better reflect its behavior.src/main/java/org/example/service/UserService.java (1)
46-47: Externalize and de-duplicate token expiry configurationThe expiry durations (
1000 * 60 * 5Land1000 * 60 * 60 * 7 * 24L) are hard-coded here and (per other files) also inTokenService. Centralizing them (e.g., in configuration properties or a shared constants class) would:
- Avoid divergence between registration, login, and refresh flows,
- Make it easier to tune token lifetimes without code changes.
You might also annotate
loginUserwith@Transactionalfor clearer transactional boundaries around token updates, even though repository-level transactions already ensure atomic saves.Also applies to: 66-67
src/test/java/org/example/controller/NoteControllerTest.java (1)
95-112: Make the expired-token test less timing-sensitiveUsing
jwtUtil.generateToken("test@email.com", 1L)relies on the request being processed after that 1 ms window has passed. That’s almost always true but still timing-dependent. Consider making the token definitively expired (e.g., by passing a negative or zero duration) so the test can’t ever pass or fail based on timing.Example:
- String token = jwtUtil.generateToken("test@email.com", 1L); + String token = jwtUtil.generateToken("test@email.com", -1_000L);src/main/java/org/example/entity/UserEntity.java (1)
13-15: Consider encapsulating thenotescollection to avoid direct external mutationThe bidirectional
UserEntity↔NoteEntitymapping is fine, but exposing the mutableList<NoteEntity>directly allows callers to arbitrarily modify the persistence-managed collection.You might later want to:
- Keep the field
privateand- Add
addNote/removeNotehelpers and/or return an unmodifiable view fromgetNotes()to better control relationship consistency.Also applies to: 67-73
pom.xml (1)
121-132: Reconsider the Mockito javaagentargLineconfigurationThe Surefire config injects a Mockito javaagent using:
-javaagent:"${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar"Potential issues:
- It assumes
mockito.versionis defined and matches the jar in the local repo.- If the jar is missing, tests may fail to start due to an invalid
-javaagentpath.- Modern Mockito + JUnit setups usually don’t need this agent at all.
Unless you have a specific need for the agent, consider dropping this
argLineentirely and relying on the default Spring Boot test setup.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (40)
JavaTemplate.iml(1 hunks)docker-compose.yml(1 hunks)pom.xml(1 hunks)src/main/java/org/example/App.java(0 hunks)src/main/java/org/example/Exercise2025Application.java(1 hunks)src/main/java/org/example/config/SecurityConfig.java(1 hunks)src/main/java/org/example/controller/NoteController.java(1 hunks)src/main/java/org/example/controller/UserController.java(1 hunks)src/main/java/org/example/dto/ErrorResponse.java(1 hunks)src/main/java/org/example/dto/note/Note.java(1 hunks)src/main/java/org/example/dto/note/NoteNew.java(1 hunks)src/main/java/org/example/dto/note/NoteResponse.java(1 hunks)src/main/java/org/example/dto/token/TokenRequest.java(1 hunks)src/main/java/org/example/dto/token/UpdatedToken.java(1 hunks)src/main/java/org/example/dto/user/ApiPrincipal.java(1 hunks)src/main/java/org/example/dto/user/User.java(1 hunks)src/main/java/org/example/dto/user/UserApi.java(1 hunks)src/main/java/org/example/dto/user/UserCheck.java(1 hunks)src/main/java/org/example/dto/user/UserNew.java(1 hunks)src/main/java/org/example/dto/user/UserNotes.java(1 hunks)src/main/java/org/example/entity/NoteEntity.java(1 hunks)src/main/java/org/example/entity/TokenEntity.java(1 hunks)src/main/java/org/example/entity/UserEntity.java(1 hunks)src/main/java/org/example/exceptionHandler/ConflictException.java(1 hunks)src/main/java/org/example/exceptionHandler/GlobalExceptionHandler.java(1 hunks)src/main/java/org/example/filter/ApiKeyFilter.java(1 hunks)src/main/java/org/example/repository/NoteRepository.java(1 hunks)src/main/java/org/example/repository/TokenRepository.java(1 hunks)src/main/java/org/example/repository/UserRepository.java(1 hunks)src/main/java/org/example/service/NoteService.java(1 hunks)src/main/java/org/example/service/TokenService.java(1 hunks)src/main/java/org/example/service/UserService.java(1 hunks)src/main/java/org/example/utils/BCryptUtil.java(1 hunks)src/main/java/org/example/utils/JwtUtil.java(1 hunks)src/main/resources/application.properties(1 hunks)src/test/java/org/example/AppIT.java(0 hunks)src/test/java/org/example/AppTest.java(0 hunks)src/test/java/org/example/Exercise2025ApplicationTest.java(1 hunks)src/test/java/org/example/controller/NoteControllerTest.java(1 hunks)src/test/java/org/example/controller/UserControllerTest.java(1 hunks)
💤 Files with no reviewable changes (3)
- src/test/java/org/example/AppIT.java
- src/main/java/org/example/App.java
- src/test/java/org/example/AppTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T14:02:54.365Z
Learnt from: alfredbrannare
Repo: fungover/exercise2025 PR: 66
File: src/test/java/org/example/service/WarehouseTest.java:1-21
Timestamp: 2025-09-10T14:02:54.365Z
Learning: In the fungover/exercise2025 project, static mocking of LocalDateTime.now() works successfully with the existing Mockito setup without requiring mockito-inline dependency.
Applied to files:
src/test/java/org/example/Exercise2025ApplicationTest.java
🧬 Code graph analysis (10)
src/main/java/org/example/entity/TokenEntity.java (1)
src/main/java/org/example/entity/UserEntity.java (1)
Entity(10-98)
src/main/java/org/example/service/TokenService.java (2)
src/main/java/org/example/exceptionHandler/ConflictException.java (1)
ConflictException(3-8)src/main/java/org/example/service/UserService.java (1)
Service(21-78)
src/main/java/org/example/controller/NoteController.java (1)
src/main/java/org/example/controller/UserController.java (1)
RestController(16-46)
src/main/java/org/example/utils/JwtUtil.java (1)
src/main/java/org/example/filter/ApiKeyFilter.java (1)
Component(19-72)
src/main/java/org/example/entity/UserEntity.java (2)
src/main/java/org/example/entity/NoteEntity.java (1)
Entity(8-73)src/main/java/org/example/entity/TokenEntity.java (1)
Entity(6-89)
src/main/java/org/example/controller/UserController.java (1)
src/main/java/org/example/controller/NoteController.java (1)
RestController(15-55)
src/main/java/org/example/entity/NoteEntity.java (1)
src/main/java/org/example/entity/UserEntity.java (1)
Entity(10-98)
src/main/java/org/example/service/UserService.java (3)
src/main/java/org/example/exceptionHandler/ConflictException.java (1)
ConflictException(3-8)src/main/java/org/example/service/TokenService.java (1)
Service(13-44)src/main/java/org/example/utils/BCryptUtil.java (1)
BCryptUtil(4-14)
src/main/java/org/example/filter/ApiKeyFilter.java (1)
src/main/java/org/example/utils/JwtUtil.java (1)
Component(12-59)
src/test/java/org/example/controller/UserControllerTest.java (1)
src/test/java/org/example/Exercise2025ApplicationTest.java (1)
SpringBootTest(6-13)
🔇 Additional comments (24)
src/main/java/org/example/dto/user/ApiPrincipal.java (1)
1-3: LGTM!Clean immutable record representing the authenticated user identity. The design is appropriate for use as a security principal.
src/main/java/org/example/exceptionHandler/ConflictException.java (1)
3-8: LGTM!Clean exception design for handling user registration conflicts. The message format is clear and provides useful context.
src/main/java/org/example/dto/user/UserNotes.java (1)
1-8: LGTM!Clean DTO design for returning user notes. The structure is straightforward and appropriate for the response payload.
src/main/java/org/example/dto/token/UpdatedToken.java (1)
1-4: LGTM!Clean, minimal record for token refresh responses. The design is appropriate and straightforward.
src/main/java/org/example/dto/note/NoteResponse.java (1)
3-4: LGTM! Clean and concise DTO design.The record provides a simple, immutable data carrier for note responses. The design is appropriate for its use case.
src/main/java/org/example/dto/ErrorResponse.java (1)
3-4: LGTM! Consistent error response structure.The record provides a standardized format for error responses across the API. The design is clean and appropriate.
src/main/java/org/example/dto/note/NoteNew.java (1)
5-6: LGTM! Well-structured creation response DTO.Including the
createdAttimestamp in the response is good practice, allowing clients to display creation time without additional queries.src/main/java/org/example/dto/user/UserNew.java (1)
3-4: LGTM! Appropriate registration response structure.Returning both access token and refresh token in the registration response aligns with the PR objectives for JWT-based authentication. Ensure this endpoint is served over HTTPS to protect the tokens in transit.
src/main/java/org/example/dto/note/Note.java (1)
7-12: LGTM! Good validation and soft delete support.The
@NotNullvalidation on thevaluefield ensures data integrity, and thedeletedAtfield supports soft delete patterns, which is valuable for audit trails and data recovery.src/test/java/org/example/Exercise2025ApplicationTest.java (1)
6-11: LGTM! Standard Spring Boot smoke test.This context load test is a valuable sanity check that ensures the application context can be initialized successfully, catching configuration issues early.
src/main/java/org/example/repository/TokenRepository.java (1)
8-11: Database indexes are properly configured—no action needed.The
TokenEntityalready has appropriate indexes viaunique = trueconstraints on both thetokenandrefreshTokencolumns. In JPA/Hibernate, UNIQUE constraints automatically create database indexes, which will efficiently support thefindByToken()andfindByRefreshToken()queries. The implementation follows best practices.src/main/java/org/example/config/SecurityConfig.java (2)
27-31: LGTM! Well-structured authorization rules.The authorization configuration follows security best practices:
- Public access for authentication endpoints (
/user/**)- JWT-protected API endpoints (
/api/**)- Explicit deny-all for unspecified paths (secure default)
25-25: CSRF protection is correctly disabled for this JWT authentication flow.Verification confirms all three requirements are met:
- JWT tokens are passed via the
Authorizationheader withBearerscheme (not cookies) — confirmed inApiKeyFilter.javaand test cases- No session-based authentication is configured — no session management or cookie-related code found in the codebase
- The application is purely stateless — JWT validation is performed statelessly through the filter chain in
SecurityConfig.javaThe CSRF disabling at line 25 is appropriate and secure for this implementation.
src/main/java/org/example/repository/UserRepository.java (1)
8-11: LGTM! Clean Spring Data JPA repository.The repository interface follows Spring Data JPA best practices with properly named derived query methods and appropriate return types.
src/main/java/org/example/repository/NoteRepository.java (1)
11-13: LGTM! Well-implemented soft delete pattern.The derived query methods correctly implement soft delete filtering with proper user scoping for security.
src/main/java/org/example/service/NoteService.java (1)
27-71: Note lifecycle and ownership logic looks solidUser existence checks, per-user note creation, and soft-delete via
deletedAtwithfindByIdAndUserIdAndDeletedAtIsNullenforce correct ownership and avoid hard deletes. Transactional boundaries are appropriate.src/main/java/org/example/service/UserService.java (1)
33-75: User registration & login flows align with the token modelRegistration correctly:
- Enforces unique email via
existsByEmail,- Hashes the password before persisting the
UserEntity, and- Creates an associated
TokenEntitywith access and refresh tokens, returning them inUserNew.Login correctly:
- Looks up the user by email,
- Verifies the password via
checkPassword,- Rotates both access and refresh tokens, persists them, and
- Returns the new pair as
TokenRequest.Behavior matches the described “refresh requires refresh token + last access token” model when combined with
TokenService.src/main/java/org/example/controller/UserController.java (1)
28-38: User registration and login endpoints look consistentDTO usage, validation, and returned statuses align with the intended flows; nothing blocking here.
src/main/java/org/example/entity/NoteEntity.java (1)
8-73:NoteEntitymapping looks consistent withUserEntityThe JPA mapping (ID, user relation, value, timestamps) aligns with the usage shown in
UserEntity; no blocking issues spotted here.src/main/java/org/example/entity/TokenEntity.java (1)
6-88:TokenEntityone-to-one mapping and constraints look goodThe one-to-one association with
UserEntityand the non-null/unique constraints ontokenandrefreshTokenmatch the described token lifecycle; no blockers here.src/main/java/org/example/utils/JwtUtil.java (1)
42-56: The review comment incorrectly identifies the exception typeThe review correctly identifies that signature verification exceptions are not being caught, but it misidentifies the exception class. For JWS signature verification failures,
parseClaimsJwsthrowsio.jsonwebtoken.security.SignatureException, notSecurityException. The bareSecurityExceptioncaught on line 46 isjava.lang.SecurityException, which JJWT does not throw fromparseClaimsJwsfor signature failures.The correct fix is to catch
io.jsonwebtoken.security.SignatureException:- } catch (SecurityException e) { + } catch (io.jsonwebtoken.security.SignatureException e) { System.out.println("Invalid JWT signature: " + e.getMessage());Likely an incorrect or invalid review comment.
src/main/java/org/example/entity/UserEntity.java (2)
21-30: One-to-one token mapping looks correct; consider lifecycle expectationsThe inverse one-to-one mapping to
TokenEntitywithcascade = CascadeType.ALLis consistent withTokenEntityowning theuser_idcolumn. Just make sure this matches your lifecycle expectations (e.g., deleting a user should indeed delete their token; clearingtokenmay leave orphans unless explicitly removed).
83-97: equals/hashCode implementation is Hibernate‑friendly and id‑basedUsing the Hibernate proxy‑aware pattern and basing equality solely on non‑null
idis a solid choice for entities. Marking both methodsfinalalso prevents accidental overrides.pom.xml (1)
29-31: No issue found. Java 25 is supported by Spring Boot 3.5.6.Spring Boot 3.5.6 officially requires Java 17 as minimum and includes support for Java 24–25. The CI workflow correctly reads the
maven.compiler.releaseproperty (automatically set by the Spring Boot parent based on<java.version>) and provisions the appropriate JDK. No build incompatibility exists.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (2)
src/test/java/org/example/controller/UserControllerTest.java (1)
53-149: Tests remain order-dependent and fragile.The previous review identified that tests rely on shared DB state (the user created in
UserCanRegisterat line 55), making them order-dependent. This issue has not been addressed—there's still no@BeforeEachor per-test isolation, so running tests individually or in a different order will fail.Ensure each test seeds its required user state:
@BeforeEach void setUp() { // Register the test user that other tests depend on User testUser = new User("User", "password", "user@user.com"); try { mockMvc.perform(post("/user/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testUser))); } catch (Exception e) { // User may already exist; ignore for idempotency } }Alternatively, use unique emails per test or add a cleanup method with
@DynamicPropertySourceto reset the DB between tests.src/main/java/org/example/Exercise2025AuthApplication.java (1)
9-11: This issue has already been flagged in a previous review.The main method is missing the
public staticmodifiers, preventing the application from starting. Please refer to the existing review comment above for the fix.
🧹 Nitpick comments (13)
src/main/java/org/example/dto/user/UserCheck.java (1)
3-4: Remove unused import.The
@NotNullimport is unused—only@NotBlankis applied.Apply this diff:
import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull;src/main/java/org/example/controller/UserController.java (1)
19-20: Consider making service fields private.Public fields are unconventional for Spring-injected dependencies. Private fields with constructor injection are the standard practice.
Apply this diff:
- public final UserService userService; - public final TokenService tokenService; + private final UserService userService; + private final TokenService tokenService;src/main/java/org/example/dto/user/User.java (1)
3-4: Remove unused import.The
@NotNullimport is unused—only@NotBlankis applied.Apply this diff:
import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull;src/main/java/org/example/entity/TokenEntity.java (3)
9-11: Remove redundantnullable = falseon primary key.Primary keys are implicitly non-null, so
@Column(nullable = false)on@Idis redundant.Apply this diff:
@Id - @Column(nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
24-25: Document the purpose of the counter field.The
counterfield's purpose is unclear. Add a JavaDoc comment explaining its role (e.g., token version, usage count, or replay protection).
8-89: Consider implementing equals and hashCode for entity consistency.
UserEntityimplements proxy-awareequalsandhashCode, butTokenEntitydoes not. For consistency and to avoid collection/caching issues with Hibernate proxies, consider adding the same pattern here.Add at the end of the class:
@Override public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; TokenEntity that = (TokenEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); } @Override public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); }Don't forget to import:
import org.hibernate.proxy.HibernateProxy; import java.util.Objects;src/main/java/org/example/entity/UserEntity.java (2)
35-35: Constructor parameter name misleading.The parameter is named
passwordHashed, but the field ispassword. This suggests the password is already hashed when constructing the entity, but the parameter name could mislead callers. Consider renaming the parameter topasswordfor consistency, or ensure documentation clarifies that this expects a hashed value.Apply this diff:
- public UserEntity(Long id, String name, String passwordHashed, String email, List<NoteEntity> notes) { + public UserEntity(Long id, String name, String password, String email, List<NoteEntity> notes) { this.id = id; this.name = name; - this.password = passwordHashed; + this.password = password; this.email = email; this.notes = notes; }
20-20: Consider adding unique constraint on email field.If the business logic requires unique user emails (typical for authentication systems), add a
@Column(unique = true)constraint on the email field to enforce this at the database level.Apply this diff:
private String name; private String password; - private String email; + @Column(unique = true) + private String email;src/main/java/org/example/entity/NoteEntity.java (3)
15-17: Consider specifying FetchType.LAZY on ManyToOne.The
@ManyToOnerelationship defaults toFetchType.EAGER, which can cause N+1 query problems when loading collections of notes. Explicitly setfetch = FetchType.LAZYto avoid unnecessary joins.Apply this diff:
- @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private UserEntity user;
18-18: Consider adding validation or constraints on value field.The
valuefield has no@Columnconstraints or validation. If notes should never be empty or null, addnullable = falseor a length constraint.Apply this diff:
- private String value; + @Column(nullable = false) + private String value;
10-73: Consider implementing equals and hashCode.For consistency with
UserEntityand to avoid collection/caching issues with Hibernate proxies, consider adding proxy-awareequalsandhashCodemethods.Add at the end of the class:
@Override public final boolean equals(Object o) { if (this == o) return true; if (o == null) return false; Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; NoteEntity that = (NoteEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); } @Override public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); }Don't forget to import:
import org.hibernate.proxy.HibernateProxy; import java.util.Objects;README.md (1)
1-9: Provide more detailed setup instructions.The current documentation is too minimal to be actionable. Consider adding:
- Specific Docker commands (e.g.,
docker-compose up)- How to run the application (e.g.,
mvn spring-boot:run)- Required environment variables or configuration
- Default port and endpoints
- Any prerequisites (Java version, Maven version)
pom.xml (1)
17-28: Remove or populate empty metadata sections.The
<licenses>,<developers>, and<scm>sections contain empty elements. Either remove these sections or populate them with actual information to maintain clean project metadata.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
README.md(1 hunks)pom.xml(1 hunks)src/main/java/org/example/Exercise2025AuthApplication.java(1 hunks)src/main/java/org/example/controller/UserController.java(1 hunks)src/main/java/org/example/dto/token/TokenRequest.java(1 hunks)src/main/java/org/example/dto/user/User.java(1 hunks)src/main/java/org/example/dto/user/UserCheck.java(1 hunks)src/main/java/org/example/entity/NoteEntity.java(1 hunks)src/main/java/org/example/entity/TokenEntity.java(1 hunks)src/main/java/org/example/entity/UserEntity.java(1 hunks)src/main/java/org/example/exceptionHandler/GlobalExceptionHandler.java(1 hunks)src/main/java/org/example/service/TokenService.java(1 hunks)src/main/java/org/example/utils/BCryptUtil.java(1 hunks)src/main/resources/application-dev.properties(1 hunks)src/main/resources/application.properties(1 hunks)src/test/java/org/example/controller/NoteControllerTest.java(1 hunks)src/test/java/org/example/controller/UserControllerTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- src/main/resources/application.properties
- src/main/java/org/example/exceptionHandler/GlobalExceptionHandler.java
- src/main/java/org/example/utils/BCryptUtil.java
- src/main/java/org/example/service/TokenService.java
- src/main/java/org/example/dto/token/TokenRequest.java
🧰 Additional context used
🧬 Code graph analysis (6)
src/test/java/org/example/controller/NoteControllerTest.java (1)
src/test/java/org/example/controller/UserControllerTest.java (1)
Testcontainers(24-150)
src/main/java/org/example/controller/UserController.java (1)
src/main/java/org/example/controller/NoteController.java (1)
RestController(15-55)
src/main/java/org/example/entity/NoteEntity.java (1)
src/main/java/org/example/entity/UserEntity.java (1)
Entity(10-98)
src/main/java/org/example/entity/TokenEntity.java (2)
src/main/java/org/example/entity/NoteEntity.java (1)
Entity(8-73)src/main/java/org/example/entity/UserEntity.java (1)
Entity(10-98)
src/test/java/org/example/controller/UserControllerTest.java (1)
src/test/java/org/example/controller/NoteControllerTest.java (1)
Testcontainers(24-173)
src/main/java/org/example/entity/UserEntity.java (2)
src/main/java/org/example/entity/NoteEntity.java (1)
Entity(8-73)src/main/java/org/example/entity/TokenEntity.java (1)
Entity(6-89)
🔇 Additional comments (4)
src/test/java/org/example/controller/NoteControllerTest.java (1)
132-132: LGTM! Hardcoded IDs replaced with dynamic retrieval.The previous review flagged hardcoded note IDs. This has been fixed—both tests now retrieve the actual persisted note ID from the repository.
Also applies to: 164-164
src/main/java/org/example/entity/UserEntity.java (1)
11-11: LGTM! Reserved keyword issue resolved.The previous review flagged the use of reserved SQL keyword
"user". This has been fixed—the table is now named"users".src/main/resources/application-dev.properties (1)
1-15: Note: Inconsistency between AI summary and actual code.The AI summary indicates this file uses environment variables like
${DATABASE_URL}, but the actual code contains hardcoded values. This inconsistency has been addressed in the above comments.pom.xml (1)
30-30: No issues found—Java 25 is released and stable for production use.Java 25 reached General Availability on September 16, 2025, with GA builds available for production use from major vendors including Oracle and Microsoft. The configuration in pom.xml is valid.
Likely an incorrect or invalid review comment.
…cise2025 into kirill9m/exercise8
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
pom.xml (1)
64-68: Address the outdated jbcrypt dependency (unresolved from previous review).This is a carryover from the previous review. jbcrypt 0.4 was released in February 2017 and the original project is no longer actively maintained. For improved security and maintenance, migrate to a modern alternative: either use a maintained fork like
at.favre.lib:bcrypt, or for new projects, prefer a memory-hard algorithm like Argon2 viaargon2-jvmorPassword4j.
🧹 Nitpick comments (3)
pom.xml (1)
16-28: Fill in empty placeholder values in project metadata.Lines 16–28 contain empty or placeholder values for
<url/>,<license/>,<developer/>, and SCM connection details. While this doesn't affect functionality, it's good practice to populate these fields with accurate project information for documentation and build metadata clarity.src/main/java/org/example/utils/JwtUtil.java (2)
20-23: Consider validating JWT secret strength during initialization.The
init()method initializes the key from the injected secret but doesn't validate that the secret meets minimum security requirements (e.g., sufficient length/entropy). A weak or missing secret could compromise the entire authentication system.Consider adding validation in the
init()method:@PostConstruct public void init() { + if (jwtSecret == null || jwtSecret.isEmpty()) { + throw new IllegalStateException("JWT secret is not configured"); + } + byte[] decodedKey = Decoders.BASE64.decode(jwtSecret); + if (decodedKey.length < 32) { + throw new IllegalStateException("JWT secret must be at least 256 bits (32 bytes)"); + } - this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); + this.key = Keys.hmacShaKeyFor(decodedKey); }
25-36: Add input validation for token generation.The method doesn't validate input parameters. A null email or negative expiration time could lead to unexpected behavior or security issues.
Add parameter validation:
public String generateToken(String email, Long jwtExpirationMs) { + if (email == null || email.isEmpty()) { + throw new IllegalArgumentException("Email cannot be null or empty"); + } + if (jwtExpirationMs == null || jwtExpirationMs <= 0) { + throw new IllegalArgumentException("Expiration time must be positive"); + } + long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); Date expirationDate = new Date(nowMillis + jwtExpirationMs);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pom.xml(1 hunks)src/main/java/org/example/utils/JwtUtil.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/org/example/utils/JwtUtil.java (1)
src/main/java/org/example/filter/ApiKeyFilter.java (1)
Component(19-72)
🔇 Additional comments (2)
pom.xml (2)
83-98: Excellent: JJWT dependencies upgraded to latest stable version.The three JJWT dependencies (
jjwt-api,jjwt-impl,jjwt-jackson) have been correctly updated to version 0.13.0 (the latest stable release as of August 2025), addressing the previous review feedback. This ensures you benefit from the latest security patches and bug fixes.
30-30: Java 25 is officially supported by Spring Boot 3.5.6.Spring Boot 3.5.x is documented as compatible with Java 25 (requires Java 17+ and supports up through Java 25). No compatibility issues with this configuration.
🧪 Laboration: Spring Boot REST API with JPA, MySQL 9+, and Thymeleaf
Routes documentation:

https://documenter.getpostman.com/view/41629037/2sB3WsNKCk
All /api/ routes are protected with a JWT token.
Generating a new token requires a refresh token and the last used access token.
Bonus:
Server is running on a free Oracle Cloud instance for easy testing:
http://79.72.17.10:8080
Summary by CodeRabbit
New Features
Chores
Tests