Skip to content

Feature/user accounts#3

Merged
eeebbaandersson merged 9 commits into
mainfrom
feature/user-accounts
Jul 8, 2026
Merged

Feature/user accounts#3
eeebbaandersson merged 9 commits into
mainfrom
feature/user-accounts

Conversation

@eeebbaandersson

@eeebbaandersson eeebbaandersson commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added user sign-up, sign-in, and account management screens.
    • Users can now update profile details, change passwords, and delete their account.
    • Skills are now tied to the signed-in user, with a personalized roadmap view and login-protected access.
  • Bug Fixes

    • Improved data handling so skills and account actions stay scoped to the correct user.
    • Updated the home, login, signup, and error pages for a cleaner, more consistent experience.

- Added User entity, repo, DTOs, mapper and service.
- Linked Skill entity to User via ManyToOne relationship.
- Updated SkillRepo-/ and Service to filter skills by user ID.
- Implemented basic user authentication via SecurityConfig using username/password
- Refactor User entity and introduce cleaner DTOs for secure data transfer
- Add account management HTML views and sync styling with existing UI
- Replace dashboard title emojis with custom SVG image
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@eeebbaandersson, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 379e26bc-5a28-4a56-a59b-d50166c3bb3a

📥 Commits

Reviewing files that changed from the base of the PR and between 100f1f5 and 889e03f.

📒 Files selected for processing (12)
  • src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java
  • src/main/java/org/example/devroadmapskilltracker/skill/Skill.java
  • src/main/java/org/example/devroadmapskilltracker/user/User.java
  • src/main/java/org/example/devroadmapskilltracker/user/UserMapper.java
  • src/main/java/org/example/devroadmapskilltracker/user/controller/UserController.java
  • src/main/java/org/example/devroadmapskilltracker/user/dto/CreateUserDTO.java
  • src/main/java/org/example/devroadmapskilltracker/user/dto/UpdateUserDTO.java
  • src/main/java/org/example/devroadmapskilltracker/user/service/UserService.java
  • src/main/resources/static/css/error.css
  • src/main/resources/static/css/home.css
  • src/main/resources/static/css/login-signup.css
  • src/main/resources/templates/users/account.html
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/user-accounts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (5)
src/main/resources/static/css/error.css (1)

51-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate backdrop-filter declaration in .btn-back.

backdrop-filter: blur(10px) appears at both Line 54 and Line 65. The second is redundant. Remove the duplicate.

♻️ Remove duplicate property
 .btn-back {
     display: inline-block;
     margin-top: 0.5rem;
     backdrop-filter: blur(10px);
     border: 1px solid rgba(96, 214, 250, 0.12);
     color: rgba(96, 214, 250, 0.55);
     padding: 12px 20px;
     border-radius: 12px;
     cursor: pointer;
     font-size: 0.90rem;
     font-family: inherit;
     text-decoration: none;
     align-items: center;
     justify-content: center;
-    backdrop-filter: blur(10px);
     transition: all 0.2s ease;
     margin-bottom: -20px;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/static/css/error.css` around lines 51 - 68, The .btn-back
rule in error.css has a duplicate backdrop-filter declaration, so remove the
repeated blur(10px) entry and keep only one occurrence in the .btn-back style
block to avoid redundant CSS and make the rule easier to maintain.

Source: Linters/SAST tools

src/main/resources/static/css/home.css (1)

24-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate backdrop-filter and empty line before declaration in .nav-link, .logout-form button.

backdrop-filter: blur(10px) is declared at both Line 26 and Line 39. Additionally, Stylelint flags an empty line at Line 28 before the color declaration at Line 29. Remove the duplicate property and the stray blank line.

♻️ Remove duplicate and blank line
 .nav-link, .logout-form button {
     background-color: rgba(10, 20, 25, 0.8) !important;
     backdrop-filter: blur(10px);
     border: 1px solid rgba(96, 214, 250, 0.12) !important;
     color: rgba(96, 214, 250, 0.55);
     padding: 12px 20px;
     border-radius: 12px;
     cursor: pointer;
     font-size: 0.90rem;
     font-family: inherit;
     text-decoration: none;
     display: flex;
     align-items: center;
     gap: 8px;
-    backdrop-filter: blur(10px);
     transition: all 0.2s ease;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/static/css/home.css` around lines 24 - 41, The `.nav-link,
.logout-form button` rule has a duplicated `backdrop-filter` declaration and an
extra blank line before `color`; clean up the `home.css` block by keeping only
one `backdrop-filter: blur(10px)` and removing the stray empty line so the style
block matches linting rules.

Source: Linters/SAST tools

src/main/resources/static/css/login-signup.css (1)

160-175: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

.rocket-icon uses flex alignment properties with display: block.

justify-content and align-items are ignored when display: block is set. Since the icon is centered via margin: auto, these properties are dead code. Either switch to display: flex or remove them.

♻️ Remove unused flex properties
 .rocket-icon {
     display: block;
-    justify-content: center;
-    align-items: center;
     width: 30px;
     height: 30px;
     margin: 25px auto -10px auto;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/static/css/login-signup.css` around lines 160 - 175, The
.rocket-icon rule mixes flex alignment properties with display: block, so
justify-content and align-items are unused. Update the .rocket-icon selector to
either use display: flex if you want those alignment properties to apply, or
remove the dead flex properties and keep the current centering via margin auto.
Keep the fix localized to the .rocket-icon CSS block.
src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java (1)

33-36: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add explicit logout configuration.

Spring Security provides a default logout endpoint at /logout (POST), but explicit configuration improves clarity and allows customizing the logout success URL. This is especially useful if templates need a specific redirect after logout.

♻️ Proposed logout configuration
                .formLogin(form -> form
                        .loginPage("/login")
                        .defaultSuccessUrl("/skills")
-                       .permitAll());
+                       .permitAll())
+               .logout(logout -> logout
+                       .logoutUrl("/logout")
+                       .logoutSuccessUrl("/login?logout")
+                       .permitAll());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java`
around lines 33 - 36, The SecurityConfig formLogin setup currently defines login
behavior but leaves logout implicit; add explicit logout configuration in the
same security chain. Update the SecurityFilterChain configuration to include a
logout(...) block alongside formLogin, using a clear logout success redirect
appropriate for the app (for example back to the login page) and keep it
permit-all so the endpoint remains accessible. Use the existing SecurityConfig
and SecurityFilterChain symbols to place the change cleanly.
src/main/java/org/example/devroadmapskilltracker/config/DataInitializer.java (1)

30-62: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Wrap seed operations in a single transaction.

userRepository.save and skillRepository.saveAll are individually transactional but not wrapped together. If saveAll fails, the user is persisted without skills, and the count() == 0 guard prevents re-seeding on restart, leaving the database in an inconsistent state. Since @Transactional on a CommandLineRunner lambda won't be proxied, extract the seeding logic into a separate transactional method.

♻️ Proposed refactor with transactional seeding
 `@Configuration`
 public class DataInitializer {

     private static final Logger logger = LoggerFactory.getLogger(DataInitializer.class);
     private final PasswordEncoder passwordEncoder;
+    private final SeedDataService seedDataService;

-    public DataInitializer(PasswordEncoder passwordEncoder) {
+    public DataInitializer(PasswordEncoder passwordEncoder, SeedDataService seedDataService) {
         this.passwordEncoder = passwordEncoder;
+        this.seedDataService = seedDataService;
     }

     `@Bean`
     `@Profile`("!prod")
     CommandLineRunner initDatabase(SkillRepository skillRepository, UserRepository userRepository) {
         return args -> {
             if (userRepository.count() == 0) {
-                logger.info("No skill found. Generating test user and skills...");
-                User testUser = new User();
-                testUser.setFullName("Test Developer");
-                testUser.setUsername("test");
-                testUser.setPassword(passwordEncoder.encode("secret"));
-                User savedUser = userRepository.save(testUser);
-                skillRepository.saveAll(List.of(
-                        // ... skills ...
-                ));
-                logger.info("🚀Test data has been loaded!");
+                seedDataService.seed(skillRepository, userRepository);
             }
         };
     }
 }
+
+@Service
+class SeedDataService {
+    private static final Logger logger = LoggerFactory.getLogger(SeedDataService.class);
+    private final PasswordEncoder passwordEncoder;
+
+    public SeedDataService(PasswordEncoder passwordEncoder) {
+        this.passwordEncoder = passwordEncoder;
+    }
+
+    `@Transactional`
+    public void seed(SkillRepository skillRepository, UserRepository userRepository) {
+        logger.info("No users found. Generating test user and skills...");
+        User testUser = new User();
+        testUser.setFullName("Test Developer");
+        testUser.setUsername("test");
+        testUser.setPassword(passwordEncoder.encode("secret"));
+        User savedUser = userRepository.save(testUser);
+        skillRepository.saveAll(List.of(
+                new Skill("Docker", "Learn containerization and how to manage images.", "DevOps", SkillStatus.BACKLOG, savedUser),
+                new Skill("TypeScript", "Strongly typed JavaScript for better scaling.", "Frontend", SkillStatus.BACKLOG, savedUser),
+                new Skill("Spring Boot", "Building robust backend services with Java.", "Backend", SkillStatus.IN_PROGRESS, savedUser),
+                new Skill("Thymeleaf", "Server-side template engine for modern web apps.", "Web", SkillStatus.IN_PROGRESS, savedUser),
+                new Skill("CSS Grid", "Mastering complex layouts with grid systems.", "Design", SkillStatus.IN_PROGRESS, savedUser),
+                new Skill("Java Fundamentals", "Core syntax, OOP, and collections.", "Backend", SkillStatus.MASTERED, savedUser),
+                new Skill("REST APIs", "Designing and implementing scalable endpoints.", "Backend", SkillStatus.MASTERED, savedUser)
+        ));
+        logger.info("🚀Test data has been loaded!");
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/devroadmapskilltracker/config/DataInitializer.java`
around lines 30 - 62, The seeding logic in DataInitializer.initDatabase is not
wrapped in one transaction, so a failure in skillRepository.saveAll can leave
the test user saved without skills and block reseeding on restart. Move the
actual seeding work into a separate method on DataInitializer (for example, a
transactional seedDatabase method) and call that from the CommandLineRunner so
userRepository.save and skillRepository.saveAll run atomically under the same
transaction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 46-49: The README currently documents the seeded test account
credentials, so update the login/signup section to clearly state that the
test/secret profile is for local development only. Make sure the wording around
the pre-loaded developer profile in the README warns that this account must be
removed or disabled before any production deployment.

In `@src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java`:
- Line 27: CSRF is currently disabled in SecurityConfig via http.csrf(csrf ->
csrf.disable()), which leaves form-based state-changing actions unprotected.
Re-enable CSRF in the security configuration and make sure Thymeleaf forms that
submit to controllers use th:action (not plain action) so Spring Security can
inject the token automatically; verify the affected form templates and keep the
change centered around SecurityConfig and any Thymeleaf form submissions.

In `@src/main/java/org/example/devroadmapskilltracker/skill/Skill.java`:
- Around line 152-161: The equals/hashCode implementation in Skill currently
includes the lazy ManyToOne user association, which can trigger proxy
initialization or fail on detached entities. Update Skill.equals and
Skill.hashCode to exclude user and rely on stable fields only, keeping lazy
relationships out of equality; use the Skill class’s existing identifier and
other non-lazy properties when comparing and hashing.

In
`@src/main/java/org/example/devroadmapskilltracker/user/controller/UserController.java`:
- Around line 41-67: The `UserController.updateAccount` and
`UserController.deleteAccount` endpoints trust the client-supplied `id`,
creating an IDOR risk. Update both methods to resolve the target user from the
authenticated `Principal` (or security context) and ignore the path variable for
authorization, then pass that authenticated user identity into
`userService.updateUserAccount` and `userService.deleteUserAccount`. Also update
the related form/action flow so the Thymeleaf account update/delete URLs no
longer depend on `/{id}`.

In
`@src/main/java/org/example/devroadmapskilltracker/user/dto/UpdateUserDTO.java`:
- Around line 3-7: Add Bean Validation annotations to UpdateUserDTO so
UserController.updateAccount’s `@Valid` actually enforces constraints: mark
fullName and username with `@NotBlank`, and make password optional but constrained
with `@Size`(min = 8) when supplied. Update the UpdateUserDTO record declaration
directly, keeping id unchanged and allowing blank/null password to mean no
change.

In
`@src/main/java/org/example/devroadmapskilltracker/user/service/UserService.java`:
- Around line 51-65: Update UserService.updateUserAccount so it validates
username uniqueness before calling userRepository.save(). Compare the incoming
username from UpdateUserDTO against the current User, skip the check when
unchanged, and if it differs, query the repository for an existing user with
that username and fail fast with a clear conflict-style exception before
mapping/saving. Use the existing userMapper.updateEntityFromDTO flow and keep
password handling unchanged.

In `@src/main/java/org/example/devroadmapskilltracker/user/User.java`:
- Around line 18-22: Move the bean validation from the User entity fields
fullName, username, and password to the request DTOs used by the controller,
such as CreateUserDTO and UpdateUserDTO, so `@Valid` can produce field-level
BindingResult errors. Remove the `@NotBlank` annotations from User and place the
equivalent constraints on the DTO properties that are bound in the controller
actions, keeping User focused on persistence concerns like `@Column`.

In `@src/main/java/org/example/devroadmapskilltracker/user/UserMapper.java`:
- Around line 32-36: `updateEntityFromDTO` in `UserMapper` is overwriting the
user password from `UpdateUserDTO`, which causes blank passwords to be saved
during profile updates. Remove the password assignment from this mapper so it
only maps non-sensitive fields like `fullName` and `username`, and leave all
password handling to `UserService.updateUserAccount`, where the existing
conditional encoding logic already belongs.

In `@src/main/resources/application.properties`:
- Line 16: The Spring Security debug logging setting is hardcoded in the shared
application configuration, which would enable verbose security logs in every
environment. Remove the fixed logging.level.org.springframework.security setting
from application.properties and move it to a profile-specific config or an
environment-driven override so only local/dev runs get debug output. Keep the
change scoped to the application configuration setup so production does not
inherit the debug level by default.

In `@src/main/resources/templates/users/account.html`:
- Line 24: The account update/delete flow is trusting the path-variable user ID
from the form, which allows requests to target arbitrary accounts. Update
UserController’s updateAccount and deleteAccount to derive the target user from
the authenticated principal (or explicitly reject when the submitted id does not
match the logged-in user). Adjust the account template’s form action only as
needed to keep it aligned with the authenticated user, and ensure UserService is
called only with an authorized user identity.

---

Nitpick comments:
In
`@src/main/java/org/example/devroadmapskilltracker/config/DataInitializer.java`:
- Around line 30-62: The seeding logic in DataInitializer.initDatabase is not
wrapped in one transaction, so a failure in skillRepository.saveAll can leave
the test user saved without skills and block reseeding on restart. Move the
actual seeding work into a separate method on DataInitializer (for example, a
transactional seedDatabase method) and call that from the CommandLineRunner so
userRepository.save and skillRepository.saveAll run atomically under the same
transaction.

In `@src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java`:
- Around line 33-36: The SecurityConfig formLogin setup currently defines login
behavior but leaves logout implicit; add explicit logout configuration in the
same security chain. Update the SecurityFilterChain configuration to include a
logout(...) block alongside formLogin, using a clear logout success redirect
appropriate for the app (for example back to the login page) and keep it
permit-all so the endpoint remains accessible. Use the existing SecurityConfig
and SecurityFilterChain symbols to place the change cleanly.

In `@src/main/resources/static/css/error.css`:
- Around line 51-68: The .btn-back rule in error.css has a duplicate
backdrop-filter declaration, so remove the repeated blur(10px) entry and keep
only one occurrence in the .btn-back style block to avoid redundant CSS and make
the rule easier to maintain.

In `@src/main/resources/static/css/home.css`:
- Around line 24-41: The `.nav-link, .logout-form button` rule has a duplicated
`backdrop-filter` declaration and an extra blank line before `color`; clean up
the `home.css` block by keeping only one `backdrop-filter: blur(10px)` and
removing the stray empty line so the style block matches linting rules.

In `@src/main/resources/static/css/login-signup.css`:
- Around line 160-175: The .rocket-icon rule mixes flex alignment properties
with display: block, so justify-content and align-items are unused. Update the
.rocket-icon selector to either use display: flex if you want those alignment
properties to apply, or remove the dead flex properties and keep the current
centering via margin auto. Keep the fix localized to the .rocket-icon CSS block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df3962ab-1bf3-4c5b-8c06-72bb4bdc61dd

📥 Commits

Reviewing files that changed from the base of the PR and between 67015b2 and 100f1f5.

⛔ Files ignored due to path filters (5)
  • screenshots/app-preview.png is excluded by !**/*.png
  • src/main/resources/static/assets/close-icon.svg is excluded by !**/*.svg
  • src/main/resources/static/assets/load-icon.svg is excluded by !**/*.svg
  • src/main/resources/static/assets/rocket-icon.svg is excluded by !**/*.svg
  • src/main/resources/static/assets/search-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (29)
  • README.md
  • pom.xml
  • src/main/java/org/example/devroadmapskilltracker/config/DataInitializer.java
  • src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java
  • src/main/java/org/example/devroadmapskilltracker/skill/DataInitializer.java
  • src/main/java/org/example/devroadmapskilltracker/skill/Skill.java
  • src/main/java/org/example/devroadmapskilltracker/skill/SkillRepository.java
  • src/main/java/org/example/devroadmapskilltracker/skill/service/SkillService.java
  • src/main/java/org/example/devroadmapskilltracker/user/User.java
  • src/main/java/org/example/devroadmapskilltracker/user/UserMapper.java
  • src/main/java/org/example/devroadmapskilltracker/user/UserRepository.java
  • src/main/java/org/example/devroadmapskilltracker/user/controller/AuthController.java
  • src/main/java/org/example/devroadmapskilltracker/user/controller/UserController.java
  • src/main/java/org/example/devroadmapskilltracker/user/dto/CreateUserDTO.java
  • src/main/java/org/example/devroadmapskilltracker/user/dto/UpdateUserDTO.java
  • src/main/java/org/example/devroadmapskilltracker/user/dto/UserDTO.java
  • src/main/java/org/example/devroadmapskilltracker/user/service/UserService.java
  • src/main/resources/application.properties
  • src/main/resources/static/css/create-update.css
  • src/main/resources/static/css/error.css
  • src/main/resources/static/css/home.css
  • src/main/resources/static/css/login-signup.css
  • src/main/resources/templates/skills/error.html
  • src/main/resources/templates/skills/home.html
  • src/main/resources/templates/skills/update.html
  • src/main/resources/templates/users/account.html
  • src/main/resources/templates/users/login.html
  • src/main/resources/templates/users/signup.html
  • src/test/java/org/example/devroadmapskilltracker/skill/SkillServiceIntegrationTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/example/devroadmapskilltracker/skill/DataInitializer.java

Comment thread README.md
Comment on lines +46 to +49
4. **Log in or Sign up –** You can create a new account instantly, or use the pre-loaded developer profile:
* **Username:** `test`
* **Password:** `secret`
5. Let your journey begin!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Test credentials documented in README.

Documenting the test/secret credentials is convenient for development, but ensure this seeded account is removed or disabled before any production deployment. Consider noting that these credentials are for local development only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 46 - 49, The README currently documents the seeded
test account credentials, so update the login/signup section to clearly state
that the test/secret profile is for local development only. Make sure the
wording around the pre-loaded developer profile in the README warns that this
account must be removed or disabled before any production deployment.


@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
http.csrf(csrf -> csrf.disable())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Re-enable CSRF protection for form-based authentication.

Disabling CSRF protection exposes all state-changing operations (skill create/update/delete, account changes) to cross-site request forgery. Since this application uses Thymeleaf with thymeleaf-extras-springsecurity6, CSRF tokens are automatically injected into forms that use th:action, making re-enabling straightforward.

🔒 Proposed fix to re-enable CSRF protection
-       http.csrf(csrf -> csrf.disable())
-               .authorizeHttpRequests(auth -> auth
+       http
+               .authorizeHttpRequests(auth -> auth

Ensure all Thymeleaf forms use th:action instead of plain action so the CSRF token is automatically included:

<!-- Use this -->
<form th:action="@{/skills}" method="post">
<!-- Instead of this -->
<form action="/skills" method="post">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/devroadmapskilltracker/config/SecurityConfig.java`
at line 27, CSRF is currently disabled in SecurityConfig via http.csrf(csrf ->
csrf.disable()), which leaves form-based state-changing actions unprotected.
Re-enable CSRF in the security configuration and make sure Thymeleaf forms that
submit to controllers use th:action (not plain action) so Spring Security can
inject the token automatically; verify the affected form templates and keep the
change centered around SecurityConfig and any Thymeleaf form submissions.

Source: Linters/SAST tools

Comment thread src/main/java/org/example/devroadmapskilltracker/skill/Skill.java
Comment thread src/main/java/org/example/devroadmapskilltracker/user/dto/UpdateUserDTO.java Outdated
Comment on lines +18 to +22
@NotBlank(message = "A name is required") private String fullName;

@NotBlank(message = "A username is required") @Column(unique = true, nullable = false)
private String username;
@NotBlank(message = "A password is required") private String password;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validation annotations should be on the DTOs, not the entity.

@NotBlank on fullName, username, and password only triggers during JPA persistence (PrePersist/PreUpdate), not during controller-level @Valid validation on CreateUserDTO/UpdateUserDTO. This means form submissions won't produce field-level BindingResult errors — blank fields will either silently pass or throw a ConstraintViolationException at save time, resulting in a generic "unknown error" message instead of inline form validation feedback.

Move the validation constraints to the DTOs that controllers bind and validate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/example/devroadmapskilltracker/user/User.java` around lines
18 - 22, Move the bean validation from the User entity fields fullName,
username, and password to the request DTOs used by the controller, such as
CreateUserDTO and UpdateUserDTO, so `@Valid` can produce field-level BindingResult
errors. Remove the `@NotBlank` annotations from User and place the equivalent
constraints on the DTO properties that are bound in the controller actions,
keeping User focused on persistence concerns like `@Column`.

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.flyway.enabled=false

logging.level.org.springframework.security=debug

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Spring Security debug logging should not be hardcoded for all environments.

logging.level.org.springframework.security=debug outputs verbose filter chain details, authentication events, and session information to logs. Use a profile-specific override or environment variable so this doesn't leak to production.

⚙️ Proposed fix
-logging.level.org.springframework.security=debug
+logging.level.org.springframework.security=${SECURITY_LOG_LEVEL:info}
📝 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
logging.level.org.springframework.security=debug
logging.level.org.springframework.security=${SECURITY_LOG_LEVEL:info}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application.properties` at line 16, The Spring Security
debug logging setting is hardcoded in the shared application configuration,
which would enable verbose security logs in every environment. Remove the fixed
logging.level.org.springframework.security setting from application.properties
and move it to a profile-specific config or an environment-driven override so
only local/dev runs get debug output. Keep the change scoped to the application
configuration setup so production does not inherit the debug level by default.

Comment thread src/main/resources/templates/users/account.html Outdated
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@eeebbaandersson

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@eeebbaandersson
eeebbaandersson merged commit d981c7b into main Jul 8, 2026
2 checks passed
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.

1 participant