Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9855a18
Add name, displayName correctly
gvaguirres Apr 9, 2026
50fc9c8
Create v7 migration to add column email and first_name
gvaguirres Apr 9, 2026
8c4da1e
Add findByName instead of findByEmail in registration controller, use…
gvaguirres Apr 9, 2026
072a720
Fix with code rabbit
gvaguirres Apr 9, 2026
a0fd1b9
Fix SignupController to set email, firstName and lastName when creati…
gvaguirres Apr 9, 2026
95defb7
Fix findByName instead if findbyemail
gvaguirres Apr 9, 2026
36be9e7
Change e-post for användarnamn. Add dev user. Add new migration
gvaguirres Apr 9, 2026
22bd5fc
Refactor flyway migration from v7 to v9
gvaguirres Apr 10, 2026
4d750ff
Change role admin for user instead after code rabbit comment
gvaguirres Apr 10, 2026
34b9d49
Fix after code rabbit
gvaguirres Apr 10, 2026
ab05d20
Fixed conflict
gvaguirres Apr 10, 2026
220b547
Merge branch 'main' into feature/change-user-entity-add-dev-user
gvaguirres Apr 10, 2026
6715a1c
Use username as principal to match the new authentication contract
gvaguirres Apr 10, 2026
b0459bf
Add validate and pre-check username uniqueness before persisting in r…
gvaguirres Apr 10, 2026
a8f2da3
Add a DB-level uniqueness guard for email
gvaguirres Apr 10, 2026
a1cf28b
Merge remote-tracking branch 'origin/feature/change-user-entity-add-d…
gvaguirres Apr 10, 2026
7e3d189
Add static admin email
gvaguirres Apr 10, 2026
64d93b1
Normalize findByName the same way registration does
gvaguirres Apr 10, 2026
86f5b9d
Refactor logic sign up with web authn to user service
gvaguirres Apr 10, 2026
fcc43de
Removed unused imports
gvaguirres Apr 10, 2026
04b2c9a
Add null/blank validation before trimming and querying and Email-base…
gvaguirres Apr 10, 2026
2f2c0b8
Fix with code rabbit
gvaguirres Apr 10, 2026
f4368b8
Fix with code rabbit last comment
gvaguirres Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/main/java/backendlab/team4you/Team4youApplication.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package backendlab.team4you;

import backendlab.team4you.user.UserRepository;
import backendlab.team4you.user.UserEntity;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.webauthn.api.Bytes;

@SpringBootApplication
public class Team4youApplication {
Expand All @@ -10,4 +17,24 @@ public static void main(String[] args) {
SpringApplication.run(Team4youApplication.class, args);
}

@Bean
@Profile("dev")
ApplicationRunner init(UserRepository repository, BCryptPasswordEncoder encoder) {
return args -> {
if (repository.count() == 0) {

UserEntity devUser = new UserEntity(
Bytes.fromBase64("01"),
"dev", // name (username)
"Developer" // displayName
);

devUser.setPasswordHash(encoder.encode("123456"));
devUser.setRole("USER");
devUser.setEmail("dev@gmail.com");

repository.save(devUser);
}
};
}
Comment on lines +20 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hardcoding a known admin password in source control.

@Profile("dev") helps, but this still creates a predictable ADMIN credential if the profile is enabled in the wrong environment or a shared dev stack. Read the password from environment/config, or generate it at startup instead of committing "123456".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/Team4youApplication.java` around lines 20 -
39, The init ApplicationRunner currently hardcodes the dev admin password
("123456") when creating a UserEntity; replace that by reading a password from
configuration or environment (e.g., System.getenv or Spring's Environment
property) and fall back to securely generating a random password if none is
provided, then call encoder.encode on that value instead of the literal; ensure
the change is applied in the init method that constructs UserEntity and
setPasswordHash, and log or output the generated password to a dev-only sink so
operators can access it without committing secrets.

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void onAuthenticationSuccess(HttpServletRequest request,

String username = authentication.getName();

var userEntity = userService.findByEmail(username);
var userEntity = userService.findByName(username);

if (userEntity != null){
var credentials = userCredentialRepository.findByUserId(userEntity.getId());
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/backendlab/team4you/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ UserCredentialRepository userCredentialRepository(JdbcOperations jdbc) {
@Bean
public UserDetailsService userDetailsService(UserService userService){
return username -> {
UserEntity user = userService.findByEmail(username);
UserEntity user = userService.findByName(username);
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}

return User.builder()
.username(user.getEmail())
.username(user.getName())
.password(user.getPasswordHash())
.roles(user.getRole())
.accountLocked(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public RegistrationController(UserService userService){
@GetMapping("/register")
public String showRegistrationForm(Model model) {

model.addAttribute("user", new UserRegistrationDTO("", "", "", "", "", ""));
model.addAttribute("user", new UserRegistrationDTO("","", "", "", "", "", ""));
return "register";
}

Expand Down Expand Up @@ -68,8 +68,8 @@ public String loginPage(@RequestParam(value = "registered", required = false) St
public String welcome(Model model, Principal principal) {
if (principal == null) return "redirect:/login"; // Säkerhetskoll

String email = principal.getName();
UserEntity user = userService.findByEmail(email);
String name = principal.getName();
UserEntity user = userService.findByName(name);

if (user == null) {

Expand Down
61 changes: 34 additions & 27 deletions src/main/java/backendlab/team4you/controller/SignupController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,29 @@

import backendlab.team4you.user.UserEntity;
import backendlab.team4you.user.UserService;
import backendlab.team4you.user.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ResponseStatusException;

import java.security.SecureRandom;
import java.util.List;

@Controller
public class SignupController {

private final PublicKeyCredentialUserEntityRepository users;
private final SecureRandom random = new SecureRandom();
private final UserService userService;

public SignupController(PublicKeyCredentialUserEntityRepository users,
Expand Down Expand Up @@ -58,30 +52,16 @@ String signup(org.springframework.security.web.csrf.CsrfToken token, Model model
@ResponseBody
public void signup(@RequestBody SignupRequest req, HttpServletRequest request, HttpServletResponse response) {

if (req.username == null || req.username.isBlank()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username is required");
}

if (userService.findByEmail(req.username) != null) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "Username already exists");
}

byte[] idBytes = new byte[32];
random.nextBytes(idBytes);

UserEntity userEntity = new UserEntity(
new Bytes(idBytes),
req.username,
req.displayName
UserEntity userEntity = userService.registerWebAuthnUser(
req.getUsername(),
req.getDisplayName(),
req.getEmail(),
req.getFirstName(),
req.getLastName()
);

String assignedRole = req.getUsername().endsWith("@team4you.com") ? "ADMIN" : "USER";
userEntity.setRole(assignedRole);

users.save(userEntity);

Authentication auth = new UsernamePasswordAuthenticationToken(
userEntity.getName(), null, List.of(new SimpleGrantedAuthority("ROLE_USER")));
userEntity.getName(), null, List.of(new SimpleGrantedAuthority("ROLE_" + userEntity.getRole())));

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
Expand All @@ -93,6 +73,9 @@ public void signup(@RequestBody SignupRequest req, HttpServletRequest request, H
public static class SignupRequest {
private String username;
private String displayName;
private String email;
private String firstName;
private String lastName;

public SignupRequest() {
}
Expand All @@ -112,6 +95,30 @@ public String getDisplayName() {
public void setDisplayName(String displayName) {
this.displayName = displayName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import java.time.LocalDateTime;

public record UserRegistrationDTO( String firstName,
public record UserRegistrationDTO(
String name,
String firstName,
String lastName,
String email,
String phoneNumber,
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/backendlab/team4you/mapper/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static UserEntity toEntity(UserRegistrationDTO dto) {
if (dto == null) return null;

UserEntity entity = new UserEntity();
entity.setName(dto.name());
entity.setFirstName(dto.firstName());
entity.setLastName(dto.lastName());
entity.setEmail(dto.email());
Expand All @@ -36,4 +37,3 @@ public static UserDTO toDto(UserEntity entity) {


}

30 changes: 22 additions & 8 deletions src/main/java/backendlab/team4you/user/UserEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.jspecify.annotations.Nullable;
import org.springframework.security.web.webauthn.api.Bytes;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;

Expand All @@ -18,9 +19,15 @@ public class UserEntity implements PublicKeyCredentialUserEntity {
private String id;

@Column(name = "name", nullable = false, unique = true)
private String email;
private String name;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Column(name = "display_name")
private String displayName;

@Column(name = "email")
private String email;
Comment on lines +27 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Email is still treated as unique elsewhere, but this model no longer enforces it.

UserRepository.findByEmail(...) still assumes one user per email, and UserService.registerUser() still validates email uniqueness in application code. With this entity exposing email as a plain column and signup now writing it directly, duplicate emails can slip in through concurrent or alternate flows. Add a DB-level unique constraint/migration or stop using email as a unique identifier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/user/UserEntity.java` around lines 27 - 28,
The entity currently exposes the email field as a plain column while the rest of
the code (UserRepository.findByEmail and UserService.registerUser) still treats
email as unique; to fix, either restore a DB-level uniqueness constraint (add a
unique constraint on the email column in UserEntity via `@Column`(unique = true)
or `@Table`(uniqueConstraints = ...) and add a corresponding DB migration that
creates a unique index on the email column) or change UserRepository.findByEmail
and UserService.registerUser to handle non-unique emails (e.g., return/list
multiple users, change lookups to use a true unique identifier), and ensure
concurrent signup paths are addressed consistently across the codebase.


@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
Expand All @@ -41,10 +48,10 @@ public class UserEntity implements PublicKeyCredentialUserEntity {
public UserEntity() {
}

public UserEntity(Bytes id, String email, String firstName) {
public UserEntity(Bytes id, String name, String displayName) {
this.id = id != null ? id.toBase64UrlString() : null;
this.email = email;
this.firstName = firstName;
this.name = name;
this.displayName = displayName;
}

@Override
Expand All @@ -58,14 +65,21 @@ public void setId(Bytes id) {

@Override
public String getName() {
return this.email;
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String getDisplayName() {
return (this.firstName != null ? this.firstName : "") + " " + (this.lastName != null ? this.lastName : "");
public @Nullable String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}


public LocalDateTime getCreatedAt() {
return createdAt;
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/backendlab/team4you/user/UserRepository.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package backendlab.team4you.user;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
Optional<UserEntity> findByEmail(String email);
}

Optional<UserEntity> findByName(String name);
}
Loading