Skip to content

Conversation

@OtavioXimarelli
Copy link
Owner

@OtavioXimarelli OtavioXimarelli commented Jun 10, 2025

PR Type

Enhancement


Description

• Implement comprehensive Spring Security with JWT authentication
• Add user registration and login endpoints with DTOs
• Configure environment variable loading via dotenv
• Restructure security configuration and add token service


Changes walkthrough 📝

Relevant files
Formatting
1 files
AiFoodAppApplication.java
Minor code formatting improvement                                               
+1/-1     
Configuration changes
5 files
CorsConfig.java
Remove unused CORS configuration class                                     
+0/-31   
DotenvEnvironmentPostProcessor.java
Add environment variable processor for dotenv                       
+25/-0   
SecurityConfig.java
Remove old security configuration file                                     
+0/-23   
spring.factories
Register dotenv environment post processor                             
+2/-0     
application.properties
Configure environment variables and JWT settings                 
+13/-8   
Enhancement
10 files
AuthenticationController.java
Add authentication controller with login/register endpoints
+60/-0   
AuthenticationDTO.java
Add DTO for user authentication data                                         
+4/-0     
LoginResponseDTO.java
Add DTO for login response data                                                   
+4/-0     
RegisterDTO.java
Add DTO for user registration data                                             
+28/-0   
User.java
Add constructor and improve code formatting                           
+8/-1     
UserRepository.java
Update method signature and import organization                   
+4/-6     
SecurityConfig.java
Implement comprehensive security configuration with JWT   
+52/-0   
SecurityFilter.java
Add JWT authentication filter for request processing         
+51/-0   
TokenService.java
Implement JWT token generation and validation service       
+52/-0   
AuthorizationService.java
Update user loading logic for authentication                         
+5/-2     
Documentation
1 files
README.MD
Update documentation for security and authentication features
+98/-103
Dependencies
1 files
pom.xml
Add security dependencies and reorganize structure             
+129/-129

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features

      • Introduced user authentication and registration endpoints with JWT-based security.
      • Added secure password handling and role-based access control.
      • Endpoints for login and registration now available; other endpoints require authentication.
    • Configuration

      • Environment variables are now managed via a .env file for sensitive data.
      • Application properties and Docker instructions updated to support environment-based configuration.
    • Documentation

      • README updated to reflect new authentication features, security architecture, and setup instructions.
    • Dependency Updates

      • Added dependencies for Spring Security, JWT, environment variable management, and reactive programming.

    OtavioXimarelli and others added 30 commits June 1, 2025 23:32
    @coderabbitai
    Copy link
    Contributor

    coderabbitai bot commented Jun 10, 2025

    Walkthrough

    This update introduces user authentication and security features using Spring Security and JWT. It adds new DTOs, controllers, and security services, updates the project documentation, adjusts configuration to support environment variables via dotenv, and reorganizes API endpoints. The build configuration receives new dependencies for security and environment management, and some legacy configuration files are removed or replaced.

    Changes

    File(s) Change Summary
    README.MD Updated documentation for authentication, security, JWT, dotenv usage, API structure, and project setup.
    pom.xml Added dependencies for Spring Security, WebFlux, JWT, dotenv; removed surefire plugin; reorganized dependency order.
    src/main/java/com/otavio/aifoodapp/AiFoodAppApplication.java Minor formatting change in annotation spacing.
    src/main/java/com/otavio/aifoodapp/config/CorsConfig.java Deleted permissive global CORS configuration class.
    src/main/java/com/otavio/aifoodapp/config/DotenvEnvironmentPostProcessor.java Added class to load environment variables from .env files at startup.
    src/main/java/com/otavio/aifoodapp/config/SecurityConfig.java Deleted old security configuration class.
    src/main/java/com/otavio/aifoodapp/controller/AuthenticationController.java Added REST controller for login and registration endpoints using JWT.
    src/main/java/com/otavio/aifoodapp/dto/AuthenticationDTO.java Added DTO record for login credentials.
    src/main/java/com/otavio/aifoodapp/dto/LoginResponseDTO.java Added DTO record for login response with JWT and user info.
    src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java Added DTO class for user registration data.
    src/main/java/com/otavio/aifoodapp/model/User.java Added constructor for login/password/role; formatted getAuthorities method.
    src/main/java/com/otavio/aifoodapp/repository/UserRepository.java Changed findByLogin to return UserDetails instead of Optional<UserDetails>.
    src/main/java/com/otavio/aifoodapp/security/SecurityConfig.java Added new security configuration: stateless, JWT filter, endpoint access rules, password encoder.
    src/main/java/com/otavio/aifoodapp/security/SecurityFilter.java Added JWT-based authentication filter for incoming requests.
    src/main/java/com/otavio/aifoodapp/security/TokenService.java Added service for generating and validating JWT tokens.
    src/main/java/com/otavio/aifoodapp/service/AuthorizationService.java Changed user lookup from Optional to null-check style.
    src/main/resources/META-INF/spring.factories Registered DotenvEnvironmentPostProcessor with Spring Boot.
    src/main/resources/application.properties Switched to environment variable-based config, added JWT secret property, enabled Spring Security TRACE logging.

    Sequence Diagram(s)

    sequenceDiagram
        participant Client
        participant AuthController
        participant AuthManager
        participant UserRepo
        participant TokenService
    
        Client->>AuthController: POST /api/auth/login (login, password)
        AuthController->>AuthManager: authenticate(login, password)
        AuthManager->>UserRepo: findByLogin(login)
        UserRepo-->>AuthManager: UserDetails / null
        AuthManager-->>AuthController: Auth result
        alt Success
            AuthController->>TokenService: generateToken(User)
            TokenService-->>AuthController: JWT token
            AuthController-->>Client: 200 OK (token, login, role)
        else Failure
            AuthController-->>Client: 401 Unauthorized
        end
    
    Loading
    sequenceDiagram
        participant Client
        participant SecurityFilter
        participant TokenService
        participant UserRepo
        participant SecurityContext
    
        Client->>SecurityFilter: Any request with Authorization: Bearer <token>
        SecurityFilter->>TokenService: validateToken(token)
        TokenService-->>SecurityFilter: login / error
        alt Valid login
            SecurityFilter->>UserRepo: findByLogin(login)
            UserRepo-->>SecurityFilter: UserDetails / null
            SecurityFilter->>SecurityContext: setAuthentication(UserDetails)
        end
        SecurityFilter-->>Client: Continue request processing
    
    Loading

    Poem

    A hop, a leap, a password strong,
    JWTs now guard where bunnies belong.
    Logins are safe, with tokens in tow,
    Spring Security shields as data flows.
    With dotenv secrets tucked away,
    This rabbit’s app is safe today!
    🐇🔐✨

    ✨ Finishing Touches
    • 📝 Generate Docstrings

    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
    🪧 Tips

    Chat

    There are 3 ways to chat with CodeRabbit:

    • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
      • I pushed a fix in commit <commit_id>, please review it.
      • Explain this complex logic.
      • Open a follow-up GitHub issue for this discussion.
    • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
      • @coderabbitai explain this code block.
      • @coderabbitai modularize this function.
    • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
      • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @coderabbitai read src/utils.ts and explain its main purpose.
      • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
      • @coderabbitai help me debug CodeRabbit configuration file.

    Support

    Need help? Create a ticket on our support page for assistance with any issues or questions.

    Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

    CodeRabbit Commands (Invoked using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration File (.coderabbit.yaml)

    • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
    • Please see the configuration documentation for more information.
    • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

    Documentation and Community

    • Visit our Documentation for detailed information on how to use CodeRabbit.
    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.

    @qodo-code-review
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 Security concerns

    Sensitive information exposure:
    The application.properties file previously contained hardcoded API keys (maritaca.api.key=107504935113477439114_ea6f180a4332d6ed) which were removed but may still exist in git history. JWT secret validation in TokenService throws RuntimeException with potentially sensitive error details. The authentication controller returns generic error responses that could be improved for security. Additionally, the dotenv configuration loads all environment variables without filtering, which could accidentally expose sensitive data.

    ⚡ Recommended focus areas for review

    Syntax Error

    Missing closing brace in password() method causing compilation error

        public String password() { return password;
    }
    Security Issue

    JWT validation throws RuntimeException instead of returning null/empty, which could expose sensitive error information and break authentication flow

    } catch (JWTVerificationException verificationException) {
        throw new RuntimeException("Invalid token", verificationException);
    }
    Validation Issue

    User registration endpoint lacks proper validation for duplicate users and password strength requirements

    public ResponseEntity<?> register(@RequestBody @Valid RegisterDTO data) {
        if (userRepository.findByLogin(data.login()) != null) return ResponseEntity.badRequest().build();

    @coderabbitai coderabbitai bot changed the title @coderabbitai Add Spring Security with JWT authentication and environment variable support Jun 10, 2025
    @qodo-code-review
    Copy link

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Return null for invalid tokens

    The method should return null for invalid tokens instead of throwing a
    RuntimeException. This allows the SecurityFilter to handle invalid tokens
    gracefully by not authenticating the user, rather than causing the entire
    request to fail.

    src/main/java/com/otavio/aifoodapp/security/TokenService.java [34-45]

     public String validateToken(String token) {
         try {
             Algorithm algorithm = Algorithm.HMAC256(secret);
             return JWT.require(algorithm)
                     .withIssuer("auth-api")
                     .build()
                     .verify(token)
                     .getSubject();
         } catch (JWTVerificationException verificationException) {
    -        throw new RuntimeException("Invalid token", verificationException);
    +        return null;
         }
     }
    • Apply / Chat
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly identifies that throwing a RuntimeException for an invalid token is not ideal. Returning null allows the SecurityFilter to handle this case gracefully by not authenticating the user, which is the correct behavior, thus improving the application's robustness.

    Medium
    General
    Add null checks for environment entries

    Add null check for dotenv entries to prevent NullPointerException when the .env
    file is malformed or contains null values. This ensures the application starts
    gracefully even with problematic environment files.

    src/main/java/com/otavio/aifoodapp/config/DotenvEnvironmentPostProcessor.java [14-22]

     public class DotenvEnvironmentPostProcessor implements EnvironmentPostProcessor {
         @Override
         public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
             Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
             Map<String, Object> envMap = new HashMap<>();
    -        dotenv.entries().forEach(entry -> envMap.put(entry.getKey(), entry.getValue()));
    +        dotenv.entries().forEach(entry -> {
    +            if (entry.getKey() != null && entry.getValue() != null) {
    +                envMap.put(entry.getKey(), entry.getValue());
    +            }
    +        });
             environment.getPropertySources().addFirst(new MapPropertySource("dotenv", envMap));
         }
     }
    • Apply / Chat
    Suggestion importance[1-10]: 7

    __

    Why: The suggestion correctly points out a potential NullPointerException if the .env file contains entries with null keys or values. Adding null checks makes the environment property loading more robust and prevents application startup failures due to a malformed .env file.

    Medium
    Fix method formatting consistency

    The method has incorrect formatting with a missing closing brace on the same
    line. This creates inconsistent code style and potential parsing issues.

    src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java [16-17]

    -public String password() { return password;
    +public String password() { 
    +    return password;
     }
    • Apply / Chat
    Suggestion importance[1-10]: 3

    __

    Why: The suggestion correctly points out inconsistent code formatting in the password() method. While this is a valid style improvement for readability and consistency, it has a low impact on the application's functionality.

    Low
    • More

    Copy link
    Contributor

    @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: 10

    🔭 Outside diff range comments (1)
    src/main/java/com/otavio/aifoodapp/model/User.java (1)

    33-36: ⚠️ Potential issue

    Use String-Based Enum Mapping
    The role field lacks an @Enumerated(EnumType.STRING) annotation, causing JPA to default to ordinal storage (fragile if enum order changes).

    Apply this diff to ensure string mapping:

     import com.otavio.aifoodapp.enums.UserRoles;
    +import jakarta.persistence.EnumType;
    +import jakarta.persistence.Enumerated;
    
     ...
    -    private UserRoles role;
    +    @Enumerated(EnumType.STRING)
    +    private UserRoles role;
    🧹 Nitpick comments (10)
    src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java (1)

    5-9: Add Validation Annotations
    Consider enforcing input constraints at the DTO level (e.g., @NotBlank on login and password, @NotNull on role) to catch invalid data before hitting the service layer.

    src/main/java/com/otavio/aifoodapp/model/User.java (1)

    55-58: Add Braces for Clarity
    Wrap the if/else blocks in braces to improve readability and avoid mistakes when extending logic:

      @Override
      public Collection<? extends GrantedAuthority> getAuthorities() {
    -    if (this.role == UserRoles.ADMIN)
    -        return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER"));
    -    else return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    +    if (this.role == UserRoles.ADMIN) {
    +        return List.of(
    +            new SimpleGrantedAuthority("ROLE_ADMIN"),
    +            new SimpleGrantedAuthority("ROLE_USER")
    +        );
    +    } else {
    +        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    +    }
      }
    src/main/java/com/otavio/aifoodapp/dto/AuthenticationDTO.java (1)

    1-4: Record Definition is Appropriate
    Using a Java record for the authentication payload is concise and immutable. Consider adding validation annotations (@NotBlank) on its components if you need request-level validation.

    src/main/java/com/otavio/aifoodapp/dto/LoginResponseDTO.java (1)

    1-4: Login Response DTO Setup
    The record encapsulates the JWT token, username, and role. For type safety and consistency, you could use the UserRoles enum instead of String for the role field.

    src/main/java/com/otavio/aifoodapp/repository/UserRepository.java (1)

    11-11: Missing @Transactional(readOnly = true)

    Read-only repository methods benefit from marking the outer service call as @Transactional(readOnly = true) for performance and to avoid unintended writes.
    Add it in AuthorizationService.loadUserByUsername if no higher-level transaction is active.

    src/main/java/com/otavio/aifoodapp/service/AuthorizationService.java (1)

    21-27: null handling is fine, but an Optional avoids the extra check

    The current code is correct, yet you could keep the previous Optional contract and streamline the flow:

    -UserDetails user = userRepository.findByLogin(username);
    -if (user == null) {
    -    throw new UsernameNotFoundException("User not found with username: " + username);
    -}
    -return user;
    +return userRepository.findByLogin(username)
    +        .orElseThrow(() ->
    +            new UsernameNotFoundException("User not found with username: " + username));

    No functional change, but it reads tighter and removes the possibility of forgetting the null-check elsewhere.

    src/main/java/com/otavio/aifoodapp/config/DotenvEnvironmentPostProcessor.java (1)

    17-21: .env values now override OS env vars – risky for prod

    environment.getPropertySources().addFirst(...) gives .env highest precedence, so a stale local file silently overrides real secrets set on the host.

    Safer order:

    -        environment.getPropertySources().addFirst(new MapPropertySource("dotenv", envMap));
    +        environment.getPropertySources().addLast(new MapPropertySource("dotenv", envMap));

    This keeps .env as a fallback while still allowing container/orchestration-level variables to win.

    README.MD (1)

    19-29: Minor PT-BR agreement issue

    “Framework para aplicações Java” → “Framework para aplicação Java”
    (Not blocking, just polishing copy.)

    🧰 Tools
    🪛 LanguageTool

    [grammar] ~19-~19: Possível erro de concordância de número.
    Context: ...adas - Spring Boot: Framework para aplicações Java - Spring Data JPA: Persistência de ...

    (GENERAL_NUMBER_AGREEMENT_ERRORS)

    src/main/java/com/otavio/aifoodapp/controller/AuthenticationController.java (2)

    40-47: Expose authorities as raw toString()

    auth.getAuthorities().toString() leaks internal representation ([ROLE_USER]).
    Return a structured list instead:

    -return ResponseEntity.ok(new LoginResponseDTO(token, data.login(), auth.getAuthorities().toString()));
    +var roles = auth.getAuthorities()
    +                .stream()
    +                .map(GrantedAuthority::getAuthority)
    +                .toList();
    +return ResponseEntity.ok(new LoginResponseDTO(token, data.login(), roles));

    49-59: register endpoint should return 201 + Location

    Semantically this creates a resource; prefer 201 Created and perhaps echo the user id.

    -userRepository.save(newUser);
    -return ResponseEntity.ok().build();
    +User saved = userRepository.save(newUser);
    +URI location = URI.create("/api/users/" + saved.getId());
    +return ResponseEntity.created(location).build();
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 1db51c2 and 259fcb2.

    📒 Files selected for processing (18)
    • README.MD (1 hunks)
    • pom.xml (1 hunks)
    • src/main/java/com/otavio/aifoodapp/AiFoodAppApplication.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/config/CorsConfig.java (0 hunks)
    • src/main/java/com/otavio/aifoodapp/config/DotenvEnvironmentPostProcessor.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/config/SecurityConfig.java (0 hunks)
    • src/main/java/com/otavio/aifoodapp/controller/AuthenticationController.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/dto/AuthenticationDTO.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/dto/LoginResponseDTO.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/model/User.java (2 hunks)
    • src/main/java/com/otavio/aifoodapp/repository/UserRepository.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/security/SecurityConfig.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/security/SecurityFilter.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/security/TokenService.java (1 hunks)
    • src/main/java/com/otavio/aifoodapp/service/AuthorizationService.java (1 hunks)
    • src/main/resources/META-INF/spring.factories (1 hunks)
    • src/main/resources/application.properties (1 hunks)
    💤 Files with no reviewable changes (2)
    • src/main/java/com/otavio/aifoodapp/config/CorsConfig.java
    • src/main/java/com/otavio/aifoodapp/config/SecurityConfig.java
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    src/main/java/com/otavio/aifoodapp/controller/AuthenticationController.java (1)
    src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java (1)
    • RegisterDTO (5-27)
    🪛 LanguageTool
    README.MD

    [grammar] ~19-~19: Possível erro de concordância de número.
    Context: ...adas - Spring Boot: Framework para aplicações Java - Spring Data JPA: Persistência de ...

    (GENERAL_NUMBER_AGREEMENT_ERRORS)

    🔇 Additional comments (8)
    src/main/java/com/otavio/aifoodapp/AiFoodAppApplication.java (1)

    8-8: Approve minor formatting change
    Removing the extra spaces inside the {} in the exclude attribute is purely stylistic and has no impact on functionality.

    src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java (2)

    10-14: Constructor Implementation is Correct
    The parameterized constructor properly initializes all fields, aligning with the DTO’s purpose. The use of final fields ensures immutability.


    20-26: Sensitive Field Exclusion in toString()
    Great job omitting the password from toString(). This prevents accidental logging of sensitive data.

    src/main/java/com/otavio/aifoodapp/model/User.java (1)

    37-41: Additional Constructor for Registration
    The new constructor correctly initializes login, password, and role, supporting the registration flow.

    src/main/resources/META-INF/spring.factories (1)

    1-2: Register DotenvEnvironmentPostProcessor
    The entry correctly binds the custom environment post-processor for loading .env files at startup.

    src/main/java/com/otavio/aifoodapp/security/SecurityConfig.java (1)

    47-50: Expose the same PasswordEncoder bean id used by AuthenticationManager

    If you later rely on @Bean PasswordEncoder passwordEncoder() injection elsewhere, name clashes are fine; however make sure the encoder is referenced in user-creation flow (UserDetailsService / registration).

    src/main/java/com/otavio/aifoodapp/security/TokenService.java (1)

    17-18: Make secret final & validate presence

    secret is injected but never validated. If the env/property is missing the app will start and NPE at first call.
    Mark the field final and assert it’s non-blank in a @PostConstruct.

    pom.xml (1)

    32-39: Mixing spring-boot-starter-web and webflux often causes servlet/reactive clashes

    Running both starters drags in two HTTP engines (Tomcat & Netty) and duplicate DispatcherHandlers.
    Unless you truly need hybrid mode, keep only one to avoid ambiguous bean errors.

    Comment on lines +15 to +18
    public String login() {return login;}
    public String password() { return password;
    }
    public UserRoles role() {return role;}
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Syntax Error in password() Getter
    The password() method is missing its closing brace, causing a compilation error.

    Apply this diff to fix the getter:

    -    public String password() { return password;
    -}
    +    public String password() {
    +        return 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 String login() {return login;}
    public String password() { return password;
    }
    public UserRoles role() {return role;}
    public String login() {return login;}
    public String password() {
    return password;
    }
    public UserRoles role() {return role;}
    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/dto/RegisterDTO.java between lines 15 and
    18, the password() getter method is missing its closing brace, causing a syntax
    error. Add the missing closing brace at the end of the password() method to
    properly close the method and fix the compilation error.
    

    Comment on lines +8 to 12
    public interface UserRepository extends JpaRepository<User, Long> {


    UserDetails findByLogin(String login);

    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Return type may confuse JPA projections

    Spring Data usually returns the entity type (User) for finder methods.
    Returning UserDetails works only if User implements that interface, but it obscures the intent and prevents using repository-level type safety for writes/updates.

    Consider:

    -UserDetails findByLogin(String login);
    +User findByLogin(String login);          // keeps repo bound to the entity

    Let the service layer convert UserUserDetails (or simply return the entity if it implements the interface).
    This keeps repository concerns (persistence) separate from security concerns.

    📝 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 interface UserRepository extends JpaRepository<User, Long> {
    UserDetails findByLogin(String login);
    public interface UserRepository extends JpaRepository<User, Long> {
    // keeps repo bound to the entity
    User findByLogin(String login);
    }
    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/repository/UserRepository.java around
    lines 8 to 12, the method findByLogin returns UserDetails, which can confuse JPA
    projections and reduce type safety. Change the return type of findByLogin to
    User to keep repository concerns focused on persistence. Then, handle the
    conversion from User to UserDetails in the service layer to separate persistence
    logic from security concerns.
    

    Comment on lines +21 to +22
    logging.level.org.springframework.security=TRACE

    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    TRACE security logging exposes sensitive information

    logging.level.org.springframework.security=TRACE dumps credentials, JWTs and headers to logs.
    Keep it only for local debugging and override to INFO (or remove) for any non-dev profile.

    🤖 Prompt for AI Agents
    In src/main/resources/application.properties at lines 21 to 22, the logging
    level for org.springframework.security is set to TRACE, which exposes sensitive
    information such as credentials, JWTs, and headers. Change this logging level to
    INFO or remove the line entirely for any non-development profiles to prevent
    sensitive data from being logged in production environments. Keep TRACE level
    only for local debugging purposes.
    

    Comment on lines +23 to +24
    # Adicione esta propriedade para carregar o segredo do JWT
    api.security.token.secret=${JWT_SECRET:dever-ser-definido-no-dotenv}
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Do not ship a default JWT secret

    api.security.token.secret=${JWT_SECRET:dever-ser-definido-no-dotenv} means tests & careless deployments will run with the literal string, weakening security.

    At minimum, fail fast when the env var is absent:

    -api.security.token.secret=${JWT_SECRET:dever-ser-definido-no-dotenv}
    +api.security.token.secret=${JWT_SECRET}

    and document the requirement in README.

    🤖 Prompt for AI Agents
    In src/main/resources/application.properties at lines 23-24, remove the default
    JWT secret value to avoid using a weak literal string. Instead, configure the
    application to fail fast or throw an error if the JWT_SECRET environment
    variable is not set. Additionally, update the README to clearly document the
    requirement to set this environment variable before running the application.
    

    Comment on lines +32 to +38
    .authorizeHttpRequests(authorize -> authorize
    .requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
    .requestMatchers(HttpMethod.POST, "/api/auth/register").permitAll()
    .anyRequest().authenticated()
    )
    .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)

    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Pre-flight CORS / OPTIONS requests will be rejected

    Because CORS config was removed, browsers send an OPTIONS pre-flight that now hits .anyRequest().authenticated() and returns 401/403.

    Allow all OPTIONS and enable CORS:

    +                .cors(AbstractHttpConfigurer::disable) // if you intentionally manage CORS elsewhere
                     .authorizeHttpRequests(authorize -> authorize
                             .requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
                             .requestMatchers(HttpMethod.POST, "/api/auth/register").permitAll()
    +                        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                             .anyRequest().authenticated()
                     )

    Alternatively, re-introduce a central @Bean CorsConfigurationSource.

    Committable suggestion skipped: line range outside the PR's diff.

    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/security/SecurityConfig.java around lines
    32 to 38, the current security configuration rejects pre-flight CORS OPTIONS
    requests because they are not explicitly permitted and fall under
    .anyRequest().authenticated(). To fix this, update the authorizeHttpRequests
    configuration to explicitly permit all OPTIONS requests by adding a matcher for
    HttpMethod.OPTIONS with permitAll(). Additionally, enable CORS support by
    calling .cors() in the HttpSecurity configuration or reintroduce a central @Bean
    CorsConfigurationSource to define CORS settings globally.
    

    Comment on lines +26 to +28
    .withExpiresAt(genExperationDate())
    .sign(algorithm);

    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Rename typo + rely on UTC for expiry

    genExperationDate is miss-spelled and the fixed -03:00 offset bakes a regional assumption into every token.

    -private Instant genExperationDate() {
    -    return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
    +private Instant generateExpirationInstant() {
    +    return Instant.now().plus(Duration.ofHours(2));   // always UTC
    }
    ...
    -.withExpiresAt(genExperationDate())
    +.withExpiresAt(generateExpirationInstant())

    Also applies to: 47-49

    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/security/TokenService.java around lines
    26-28 and 47-49, rename the method genExperationDate to genExpirationDate to fix
    the typo. Additionally, update the expiration date calculation to use UTC time
    instead of a fixed -03:00 offset to avoid embedding a regional assumption in the
    token expiry. Adjust the code to generate the expiration date based on UTC time
    zone consistently.
    

    Comment on lines +34 to +45
    public String validateToken(String token) {
    try {
    Algorithm algorithm = Algorithm.HMAC256(secret);
    return JWT.require(algorithm)
    .withIssuer("auth-api")
    .build()
    .verify(token)
    .getSubject();
    } catch (JWTVerificationException verificationException) {
    throw new RuntimeException("Invalid token", verificationException);
    }
    }
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Return 401 instead of 500 on invalid JWT

    validateToken throws RuntimeException; the filter will bubble it up as a 500 error.
    Wrap the call and, on JWTVerificationException, simply skip authentication so Spring Security turns the request into a 401.

    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/security/TokenService.java around lines 34
    to 45, the validateToken method currently throws a RuntimeException on JWT
    verification failure, causing a 500 error. Modify the method to not throw an
    exception on JWTVerificationException; instead, catch the exception and return
    null or an empty Optional to indicate invalid token. Then, in the authentication
    filter that calls validateToken, check for this null or empty result and skip
    setting authentication, allowing Spring Security to respond with a 401
    Unauthorized instead of 500.
    

    Comment on lines +46 to +50
    private String recoverToken(HttpServletRequest request) {
    var authHeader = request.getHeader("Authorization");
    if (authHeader == null) return null;
    return authHeader.replace("Bearer ", "");
    }
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Prefix check missing in recoverToken

    Unconditionally calling replace("Bearer ", "") will also strip the word Bearer from malformed headers.
    Add a prefix check:

    if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer "))
        return null;
    return authHeader.substring(7);

    Committable suggestion skipped: line range outside the PR's diff.

    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/security/SecurityFilter.java around lines
    46 to 50, the recoverToken method removes "Bearer " from the Authorization
    header without verifying if the header actually starts with "Bearer ". To fix
    this, add a check to confirm the header starts with "Bearer " before removing
    it; if it does not, return null or handle it appropriately to avoid stripping
    "Bearer" from malformed headers.
    

    Comment on lines +31 to +43
    var token = this.recoverToken(request);
    if (token != null) {
    var login = tokenService.validateToken(token);
    if (login != null && !login.isEmpty()) {
    var userExists = userRepository.findByLogin(login);
    if (userExists != null) {
    UserDetails user = userExists;
    var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    }
    }
    filterChain.doFilter(request, response);
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Unhandled RuntimeException from token validation will short-circuit the filter chain

    Catch RuntimeException around tokenService.validateToken(token) to avoid converting authentication failures into 500 responses.

    try {
        var login = tokenService.validateToken(token);
    -   if (login != null && !login.isEmpty()) {
    +   if (StringUtils.hasText(login)) {
            ...
        }
    } catch (RuntimeException ex) {
        // invalid token → leave SecurityContext empty
    }
    📝 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
    var token = this.recoverToken(request);
    if (token != null) {
    var login = tokenService.validateToken(token);
    if (login != null && !login.isEmpty()) {
    var userExists = userRepository.findByLogin(login);
    if (userExists != null) {
    UserDetails user = userExists;
    var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    }
    }
    filterChain.doFilter(request, response);
    var token = this.recoverToken(request);
    if (token != null) {
    try {
    var login = tokenService.validateToken(token);
    if (StringUtils.hasText(login)) {
    var userExists = userRepository.findByLogin(login);
    if (userExists != null) {
    UserDetails user = userExists;
    var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    }
    } catch (RuntimeException ex) {
    // invalid token → leave SecurityContext empty
    }
    }
    filterChain.doFilter(request, response);
    🤖 Prompt for AI Agents
    In src/main/java/com/otavio/aifoodapp/security/SecurityFilter.java around lines
    31 to 43, the call to tokenService.validateToken(token) can throw a
    RuntimeException that is currently unhandled, which would stop the filter chain
    and cause a 500 error. Wrap the call to validateToken in a try-catch block that
    catches RuntimeException, and handle the exception gracefully (e.g., by logging
    it and skipping authentication) so the filter chain continues without
    interruption.
    

    Comment on lines +122 to +142
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    </configuration>
    </plugin>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.3.1</version>
    <configuration>
    <encoding>UTF-8</encoding>
    <nonFilteredFileExtensions>
    <nonFilteredFileExtension>properties</nonFilteredFileExtension>
    </nonFilteredFileExtensions>
    </configuration>
    </plugin>
    </plugins>
    </build>
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Missing surefire plugin removes test execution from Maven lifecycle

    The previous maven-surefire-plugin section is gone, so mvn test will silently skip unit tests on CI.
    Re-add the plugin or enable Spring-Boot’s default surefire integration.

    🤖 Prompt for AI Agents
    In pom.xml around lines 122 to 142, the maven-surefire-plugin configuration is
    missing, causing unit tests to be skipped during the Maven test phase. To fix
    this, re-add the maven-surefire-plugin section under the build plugins with
    appropriate configuration or ensure that Spring Boot's default surefire
    integration is enabled so that tests run correctly during the Maven lifecycle.
    

    @OtavioXimarelli OtavioXimarelli deleted the SpringSecurity-implementation branch June 11, 2025 11:58
    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Olá @OtavioXimarelli - revisei suas alterações - aqui está algum feedback:

    • A chave da propriedade secreta JWT (api.security.token.secret) em TokenService não corresponde ao nome da variável .env (JWT_SECRET), então o segredo pode nunca ser injetado — alinhe esses nomes de propriedade.
    • Altere UserRepository.findByLogin para retornar um Optional (ou Optional) e trate-o em AuthorizationService para evitar possíveis NPEs em vez de depender de verificações nulas.
    • Em SecurityFilter.recoverToken(), adicione uma verificação para o prefixo 'Bearer ' (por exemplo, startsWith) e trate cabeçalhos de Autorização inválidos ou malformados de forma mais defensiva.
    Aqui está o que eu examinei durante a revisão
    • 🟡 Problemas gerais: 2 problemas encontrados
    • 🟢 Segurança: tudo parece bom
    • 🟢 Teste: tudo parece bom
    • 🟢 Complexidade: tudo parece bom
    • 🟢 Documentação: tudo parece bom

    Sourcery é gratuito para código aberto - se você gosta de nossas revisões, por favor, considere compartilhá-las ✨
    Ajude-me a ser mais útil! Por favor, clique em 👍 ou 👎 em cada comentário e eu usarei o feedback para melhorar suas revisões.
    Original comment in English

    Hey @OtavioXimarelli - I've reviewed your changes - here's some feedback:

    • The JWT secret property key (api.security.token.secret) in TokenService doesn’t match the .env variable name (JWT_SECRET), so the secret may never be injected—align these property names.
    • Switch UserRepository.findByLogin to return an Optional (or Optional) and handle it in AuthorizationService to avoid potential NPEs instead of relying on null checks.
    • In SecurityFilter.recoverToken(), add a check for the ‘Bearer ’ prefix (e.g. startsWith) and handle invalid or malformed Authorization headers more defensively.
    Here's what I looked at during the review
    • 🟡 General issues: 2 issues found
    • 🟢 Security: all looks good
    • 🟢 Testing: all looks good
    • 🟢 Complexity: all looks good
    • 🟢 Documentation: all looks good

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    return JWT.create()
    .withIssuer("auth-api")
    .withSubject(user.getUsername())
    .withExpiresAt(genExperationDate())
    Copy link

    Choose a reason for hiding this comment

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

    issue (bug_risk): withExpiresAt espera um Date, não um Instant

    Converta o Instant retornado por genExperationDate() para um Date usando Date.from(...) antes de passá-lo para withExpiresAt.

    Original comment in English

    issue (bug_risk): withExpiresAt expects a Date, not Instant

    Convert the Instant returned by genExperationDate() to a Date using Date.from(...) before passing it to withExpiresAt.

    private String email;
    private UserRoles role;

    public User(String login, String password, UserRoles role) {
    Copy link

    Choose a reason for hiding this comment

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

    issue (bug_risk): Construtor sem argumentos ausente, requerido pelo JPA

    Adicione um construtor sem argumentos protegido ou público para garantir a compatibilidade com JPA, ou use @NoArgsConstructor do Lombok.

    Original comment in English

    issue (bug_risk): Missing no-args constructor required by JPA

    Add a protected or public no-arg constructor to ensure JPA compatibility, or use Lombok's @NoArgsConstructor.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    1 participant