Enhance auth user lifecycle and registration checks

This commit is contained in:
Lucio Lelii 2026-03-26 13:01:08 +01:00
parent 7d7b131f55
commit ee14e5d7d1
14 changed files with 295 additions and 44 deletions

View File

@ -58,7 +58,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String username = jwtUtil.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
LoginEntity userDetails = authRepository.findById(username).orElse(null);
LoginEntity userDetails = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (userDetails == null) {
logger.warn("JWT username '{}' not found in database", username);
} else if (jwtUtil.validateToken(jwt, userDetails)) {

View File

@ -9,6 +9,13 @@ public class AuthRequest {
private String username;
private String password;
private String email;
private String turnstileToken;
public AuthRequest(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
// Getters e Setters
public String getUsername() { return username; }
@ -20,6 +27,9 @@ public class AuthRequest {
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getTurnstileToken() { return turnstileToken; }
public void setTurnstileToken(String turnstileToken) { this.turnstileToken = turnstileToken; }
boolean isValid() {
return username.contains(" ") && username != null && !username.isEmpty() && username.length()>5 &&
password != null && username.contains(" ") && password.length()>5 && !password.isEmpty();

View File

@ -2,13 +2,30 @@ package it.cnr.isti.workflow.manager.auth.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import it.cnr.isti.workflow.manager.auth.model.UserRole;
import java.util.List;
import java.util.Optional;
@Repository
public interface AuthRepository extends JpaRepository<LoginEntity, String> {
long countByRole(UserRole role);
@Query("select count(u) from LoginEntity u where u.role = :role and (u.active is null or u.active = true)")
long countByRoleAndActiveTrue(@Param("role") UserRole role);
@Query("select case when count(u) > 0 then true else false end from LoginEntity u where u.username = :username and (u.active is null or u.active = true)")
boolean existsByUsernameAndActiveTrue(@Param("username") String username);
@Query("select u from LoginEntity u where u.username = :username and (u.active is null or u.active = true)")
Optional<LoginEntity> findByUsernameAndActiveTrue(@Param("username") String username);
@Query("select u from LoginEntity u where u.active is null or u.active = true")
List<LoginEntity> findByActiveTrue();
}

View File

@ -18,18 +18,27 @@ public class LoginEntity {
private String username;
private String password;
private String email;
private Boolean active;
@Enumerated(EnumType.STRING)
private UserRole role;
public LoginEntity(String username, String password) {
this(username, password, null, UserRole.USER);
this(username, password, null, true, UserRole.USER);
}
public LoginEntity(String username, String password, UserRole role) {
this(username, password, null, role);
this(username, password, null, true, role);
}
public LoginEntity(String username, String password, String email, UserRole role) {
this(username, password, email, true, role);
}
public UserRole effectiveRole() {
return role == null ? UserRole.USER : role;
}
public boolean isActiveUser() {
return active == null || active;
}
}

View File

@ -3,6 +3,7 @@ package it.cnr.isti.workflow.manager.auth.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import it.cnr.isti.workflow.manager.auth.model.AdminChangePasswordResult;
import it.cnr.isti.workflow.manager.auth.model.ChangePasswordResult;
@ -13,6 +14,8 @@ import it.cnr.isti.workflow.manager.auth.model.UserRole;
import it.cnr.isti.workflow.manager.auth.model.UserView;
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
import it.cnr.isti.workflow.manager.executions.repo.ExecutionRepository;
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
import static it.cnr.isti.workflow.manager.auth.services.PasswordHasher.*;
@ -23,8 +26,14 @@ public class AuthService {
@Autowired
AuthRepository authRepository;
@Autowired
FlowRepository flowRepository;
@Autowired
ExecutionRepository executionRepository;
public boolean validateUser(String username, String password) throws UsernameNotFoundException {
LoginEntity le = authRepository.findById(username).orElse(null);
LoginEntity le = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (le == null) {
throw new UsernameNotFoundException("User not found");
}
@ -52,7 +61,7 @@ public class AuthService {
}
public ChangePasswordResult changePassword(String username, String oldPassword, String newPassword) {
LoginEntity user = authRepository.findById(username).orElse(null);
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (user == null) {
return ChangePasswordResult.USER_NOT_FOUND;
}
@ -68,7 +77,7 @@ public class AuthService {
}
public AdminChangePasswordResult adminChangePassword(String username, String newPassword) {
LoginEntity user = authRepository.findById(username).orElse(null);
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (user == null) {
return AdminChangePasswordResult.USER_NOT_FOUND;
}
@ -80,20 +89,25 @@ public class AuthService {
return AdminChangePasswordResult.SUCCESS;
}
@Transactional
public DeleteUserResult deleteUser(String username) {
LoginEntity user = authRepository.findById(username).orElse(null);
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (user == null) {
return DeleteUserResult.USER_NOT_FOUND;
}
if (user.effectiveRole() == UserRole.ADMIN && authRepository.countByRole(UserRole.ADMIN) <= 1) {
if (user.effectiveRole() == UserRole.ADMIN && authRepository.countByRoleAndActiveTrue(UserRole.ADMIN) <= 1) {
return DeleteUserResult.LAST_ADMIN;
}
authRepository.deleteById(username);
executionRepository.deleteByOwner(username);
flowRepository.deleteAll(flowRepository.findByOwnerAndFinalizedFalse(username));
user.setActive(false);
authRepository.save(user);
return DeleteUserResult.SUCCESS;
}
public ChangeUserRoleResult changeUserRole(String username, UserRole role) {
LoginEntity user = authRepository.findById(username).orElse(null);
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
if (user == null) {
return ChangeUserRoleResult.USER_NOT_FOUND;
}
@ -102,7 +116,7 @@ public class AuthService {
}
if (user.effectiveRole() == UserRole.ADMIN
&& role != UserRole.ADMIN
&& authRepository.countByRole(UserRole.ADMIN) <= 1) {
&& authRepository.countByRoleAndActiveTrue(UserRole.ADMIN) <= 1) {
return ChangeUserRoleResult.LAST_ADMIN;
}
user.setRole(role);
@ -111,14 +125,14 @@ public class AuthService {
}
public List<UserView> listUsers() {
return authRepository.findAll().stream()
return authRepository.findByActiveTrue().stream()
.map(user -> new UserView(user.getUsername(), user.getEmail(), user.effectiveRole()))
.sorted(java.util.Comparator.comparing(UserView::username))
.toList();
}
public LoginEntity getUser(String username) {
return authRepository.findById(username).orElse(null);
return authRepository.findByUsernameAndActiveTrue(username).orElse(null);
}
private boolean isValidPassword(String password) {

View File

@ -0,0 +1,65 @@
package it.cnr.isti.workflow.manager.auth.services;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
@Service
public class TurnstileService {
private static final Logger logger = LoggerFactory.getLogger(TurnstileService.class);
private final WebClient.Builder webClientBuilder;
private final boolean enabled;
private final String secret;
private final String verifyUrl;
public TurnstileService(WebClient.Builder webClientBuilder,
@Value("${app.turnstile.enabled:false}") boolean enabled,
@Value("${app.turnstile.secret:}") String secret,
@Value("${app.turnstile.verify-url:https://challenges.cloudflare.com/turnstile/v0/siteverify}") String verifyUrl) {
this.webClientBuilder = Objects.requireNonNull(webClientBuilder, "webClientBuilder cannot be null");
this.enabled = enabled;
this.secret = secret;
this.verifyUrl = verifyUrl;
}
public boolean verifyRegistrationToken(String token) {
if (!enabled) {
return true;
}
if (!StringUtils.hasText(secret)) {
logger.error("Turnstile validation is enabled but no secret is configured");
return false;
}
if (!StringUtils.hasText(token)) {
return false;
}
try {
TurnstileVerificationResponse response = webClientBuilder.build()
.post()
.uri(verifyUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("secret", secret)
.with("response", token))
.retrieve()
.bodyToMono(TurnstileVerificationResponse.class)
.block();
return response != null && Boolean.TRUE.equals(response.success());
} catch (RuntimeException exception) {
logger.warn("Turnstile verification failed", exception);
return false;
}
}
private record TurnstileVerificationResponse(Boolean success) {
}
}

View File

@ -52,6 +52,9 @@ public class UserImportComponent {
user.getUsername()),
() -> {
logger.debug("User {} not found, saving new user", user.getUsername());
if (user.getActive() == null) {
user.setActive(true);
}
user.setPassword(PasswordHasher.hashPassword(user.getPassword()));
authRepository.save(user);
});

View File

@ -15,6 +15,7 @@ import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest;
import it.cnr.isti.workflow.manager.auth.model.CreateUserResult;
import it.cnr.isti.workflow.manager.auth.model.DeleteUserResult;
import it.cnr.isti.workflow.manager.auth.services.AuthService;
import it.cnr.isti.workflow.manager.auth.services.TurnstileService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@ -45,6 +46,9 @@ public class AuthController {
@Autowired
private AuthService authService;
@Autowired
private TurnstileService turnstileService;
@PostMapping("/login")
@Operation(summary = "Login", description = "Authenticates a user and returns a JWT token on success.")
public ResponseEntity<?> login(@RequestBody AuthRequest request) {
@ -76,6 +80,9 @@ public class AuthController {
if (request.getUsername() == null || request.getPassword() == null || request.getEmail() == null) {
return ResponseEntity.badRequest().body("Username, password and email are required");
}
if (!turnstileService.verifyRegistrationToken(request.getTurnstileToken())) {
return ResponseEntity.badRequest().body("INVALID_TURNSTILE");
}
if (request.getUsername().length() < 3) {
return ResponseEntity.badRequest()
.body("Username must be at least 3 characters");

View File

@ -2,5 +2,9 @@ package it.cnr.isti.workflow.manager.executions.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ExecutionRepository extends JpaRepository<ExecutionEntity, String> {
List<ExecutionEntity> findByOwner(String owner);
void deleteByOwner(String owner);
}

View File

@ -15,4 +15,8 @@ public interface FlowRepository extends JpaRepository<FlowEntity, String> {
List<FlowEntity> findFlowsByOwnerOrPublic(@Param("owner") String owner);
java.util.Optional<FlowEntity> findByOwnerAndName(String owner, String name);
List<FlowEntity> findByOwner(String owner);
List<FlowEntity> findByOwnerAndFinalizedFalse(String owner);
}

View File

@ -37,5 +37,8 @@ app.assistant.default-model=${ASSISTANT_DEFAULT_MODEL:gemma3:12b}
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:4200}
app.import.path=${IMPORT_PATH:/workflow-editor-init}
app.import.enabled=true
app.turnstile.enabled=${TURNSTILE_ENABLED:false}
app.turnstile.secret=${TURNSTILE_SECRET:}
app.turnstile.verify-url=${TURNSTILE_VERIFY_URL:https://challenges.cloudflare.com/turnstile/v0/siteverify}
logging.level.it.cnr.isti.workflow.manager=DEBUG
logging.level.root=ERROR

View File

@ -1,7 +1,13 @@
[
{
"username": "testuser",
"password": "testpassword",
"email": "testuser@example.com"
}
{
"username": "testuser",
"password": "testpassword",
"email": "testuser@example.com"
},
{
"username": "admin",
"password": "Adminpass1!",
"email": "admin@example.com",
"role": "ADMIN"
}
]

View File

@ -3,7 +3,9 @@ package it.cnr.isti.workflow.manager.controllers;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@ -12,6 +14,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
@ -24,24 +27,50 @@ import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest;
import it.cnr.isti.workflow.manager.auth.model.UserRole;
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
import it.cnr.isti.workflow.manager.auth.services.TurnstileService;
import it.cnr.isti.workflow.manager.executions.persistence.ExecutionSnapshot;
import it.cnr.isti.workflow.manager.executions.repo.ExecutionEntity;
import it.cnr.isti.workflow.manager.executions.repo.ExecutionRepository;
import it.cnr.isti.workflow.manager.executions.ExecutionStatus;
import it.cnr.isti.workflow.manager.flows.model.FlowData;
import it.cnr.isti.workflow.manager.flows.repo.FlowEntity;
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:test.properties")
public class AuthControllerTest {
private static String uniqueSuffix() {
return java.util.UUID.randomUUID().toString().substring(0, 8);
}
@Autowired
private AuthController authController;
@Autowired
private AuthRepository authRepository;
@Autowired
private FlowRepository flowRepository;
@Autowired
private ExecutionRepository executionRepository;
@Autowired
private MockMvc mockMvc;
@Autowired
private JwtUtil jwtUtil;
@MockitoBean
private TurnstileService turnstileService;
@BeforeEach
public void configureTurnstile() {
Mockito.when(turnstileService.verifyRegistrationToken(Mockito.any())).thenReturn(true);
}
@Test
public void testLogin() {
ResponseEntity<?> response = authController.login(new AuthRequest("testuser", "testpassword", "testuser@example.com"));
@ -53,22 +82,32 @@ public class AuthControllerTest {
@Test
public void testChangePassword() {
String suffix = uniqueSuffix();
String username = "changepwd-" + suffix;
String oldPassword = "Startpass1!";
String newPassword = "Newpassword1!";
authController.register(new AuthRequest(username, oldPassword, username + "@example.com"));
ChangePasswordRequest request = new ChangePasswordRequest();
request.setUsername("testuser");
request.setOldPassword("testpassword");
request.setNewPassword("Newpassword1!");
request.setUsername(username);
request.setOldPassword(oldPassword);
request.setNewPassword(newPassword);
ResponseEntity<?> response = authController.changePassword(request);
assert response.getStatusCode().is2xxSuccessful();
ResponseEntity<?> loginResponse = authController.login(new AuthRequest("testuser", "Newpassword1!", "testuser@example.com"));
ResponseEntity<?> loginResponse = authController.login(new AuthRequest(username, newPassword, username + "@example.com"));
assert loginResponse.getStatusCode().is2xxSuccessful();
}
@Test
public void testChangePasswordReturnsUnauthorizedForInvalidCurrentPassword() {
String suffix = uniqueSuffix();
String username = "wrongold-" + suffix;
authController.register(new AuthRequest(username, "Startpass1!", username + "@example.com"));
ChangePasswordRequest request = new ChangePasswordRequest();
request.setUsername("testuser");
request.setUsername(username);
request.setOldPassword("wrong-password");
request.setNewPassword("Anotherpass1!");
@ -78,10 +117,12 @@ public class AuthControllerTest {
@Test
public void testChangePasswordReturnsInvalidNewPasswordForWeakPassword() {
authController.register(new AuthRequest("weakpwduser", "Startpass1!", "weakpwduser@example.com"));
String suffix = uniqueSuffix();
String username = "weakpwduser-" + suffix;
authController.register(new AuthRequest(username, "Startpass1!", username + "@example.com"));
ChangePasswordRequest request = new ChangePasswordRequest();
request.setUsername("weakpwduser");
request.setUsername(username);
request.setOldPassword("Startpass1!");
request.setNewPassword("weakpass");
@ -90,11 +131,28 @@ public class AuthControllerTest {
assert "INVALID_NEW_PASSWORD".equals(response.getBody());
}
@Test
public void registerRejectsInvalidTurnstile() {
Mockito.when(turnstileService.verifyRegistrationToken(Mockito.any())).thenReturn(false);
ResponseEntity<?> response = authController.register(
new AuthRequest("turnstile-user-" + uniqueSuffix(), "Validpass1!", "turnstile-user@example.com"));
assert response.getStatusCode() == HttpStatus.BAD_REQUEST;
assert "INVALID_TURNSTILE".equals(response.getBody());
}
@Test
public void adminCanCreateListChangePasswordAndDeleteUsers() {
LoginEntity admin = new LoginEntity("adminuser",
String suffix = uniqueSuffix();
String adminUsername = "adminuser-" + suffix;
String managedUsername = "manageduser-" + suffix;
String managedEmail = managedUsername + "@example.com";
String executionId = "managed-exec-" + suffix;
LoginEntity admin = new LoginEntity(adminUsername,
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
"adminuser@example.com",
adminUsername + "@example.com",
UserRole.ADMIN);
authRepository.save(admin);
@ -104,34 +162,81 @@ public class AuthControllerTest {
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
try {
ResponseEntity<?> createResponse = authController.createUser(
new AdminCreateUserRequest("manageduser", "Managedpass1!", "manageduser@example.com", UserRole.USER));
new AdminCreateUserRequest(managedUsername, "Managedpass1!", managedEmail, UserRole.USER));
assert createResponse.getStatusCode().is2xxSuccessful();
ResponseEntity<?> listResponse = authController.listUsers();
assert listResponse.getStatusCode().is2xxSuccessful();
assert listResponse.getBody() instanceof java.util.List<?>;
assert ((java.util.List<?>) listResponse.getBody()).stream()
.anyMatch(item -> item.toString().contains("manageduser") && item.toString().contains("manageduser@example.com"));
.anyMatch(item -> item.toString().contains(managedUsername) && item.toString().contains(managedEmail));
ResponseEntity<?> passwordResponse = authController.adminChangePassword(
"manageduser",
managedUsername,
new AdminChangeUserPasswordRequest("Changedpass1!"));
assert passwordResponse.getStatusCode().is2xxSuccessful();
ResponseEntity<?> roleResponse = authController.adminChangeUserRole(
"manageduser",
managedUsername,
new AdminChangeUserRoleRequest(UserRole.ADMIN));
assert roleResponse.getStatusCode().is2xxSuccessful();
assert authRepository.findById("manageduser").orElseThrow().effectiveRole() == UserRole.ADMIN;
assert authRepository.findById(managedUsername).orElseThrow().effectiveRole() == UserRole.ADMIN;
ResponseEntity<?> loginResponse = authController.login(new AuthRequest("manageduser", "Changedpass1!", "manageduser@example.com"));
ResponseEntity<?> loginResponse = authController.login(new AuthRequest(managedUsername, "Changedpass1!", managedEmail));
assert loginResponse.getStatusCode().is2xxSuccessful();
assert loginResponse.getBody() instanceof java.util.Map<?, ?>;
assert "ADMIN".equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("role"));
assert "manageduser@example.com".equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("email"));
assert managedEmail.equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("email"));
ResponseEntity<?> deleteResponse = authController.deleteUser("manageduser");
FlowEntity draftFlow = flowRepository.save(FlowEntity.builder()
.name("Managed Draft")
.owner(managedUsername)
.description("draft")
.published(true)
.finalized(false)
.createdAt(java.time.LocalDateTime.now().minusDays(1))
.lastUpdateAt(java.time.LocalDateTime.now().minusHours(1))
.flow(FlowData.builder().build())
.build());
FlowEntity finalizedFlow = flowRepository.save(FlowEntity.builder()
.name("Managed Final")
.owner(managedUsername)
.description("final")
.published(true)
.finalized(true)
.createdAt(java.time.LocalDateTime.now().minusDays(2))
.lastUpdateAt(java.time.LocalDateTime.now().minusHours(2))
.flow(FlowData.builder().build())
.build());
executionRepository.save(ExecutionEntity.builder()
.id(executionId)
.name("Managed Execution")
.owner(managedUsername)
.creationTime(100L)
.lastUpdateTime(200L)
.flow(FlowData.builder().build())
.snapshot(ExecutionSnapshot.builder()
.status(ExecutionStatus.SUCCESS)
.providedAuthorizations(java.util.Map.of())
.build())
.build());
ResponseEntity<?> deleteResponse = authController.deleteUser(managedUsername);
assert deleteResponse.getStatusCode().is2xxSuccessful();
ResponseEntity<?> deletedLoginResponse = authController.login(
new AuthRequest(managedUsername, "Changedpass1!", managedEmail));
assert deletedLoginResponse.getStatusCode() == HttpStatus.NOT_FOUND;
ResponseEntity<?> listAfterDelete = authController.listUsers();
assert listAfterDelete.getStatusCode().is2xxSuccessful();
assert listAfterDelete.getBody() instanceof java.util.List<?>;
assert ((java.util.List<?>) listAfterDelete.getBody()).stream()
.noneMatch(item -> item.toString().contains(managedUsername));
assert executionRepository.findByOwner(managedUsername).isEmpty();
assert flowRepository.findById(draftFlow.getId()).isEmpty();
assert flowRepository.findById(finalizedFlow.getId()).isPresent();
} finally {
SecurityContextHolder.clearContext();
}
@ -143,9 +248,10 @@ public class AuthControllerTest {
.filter(user -> user.effectiveRole() == UserRole.ADMIN)
.map(LoginEntity::getUsername)
.forEach(authRepository::deleteById);
LoginEntity admin = new LoginEntity("soloadmin",
String username = "soloadmin-" + uniqueSuffix();
LoginEntity admin = new LoginEntity(username,
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
"soloadmin@example.com",
username + "@example.com",
UserRole.ADMIN);
authRepository.save(admin);
@ -155,7 +261,7 @@ public class AuthControllerTest {
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
try {
ResponseEntity<?> roleResponse = authController.adminChangeUserRole(
"soloadmin",
username,
new AdminChangeUserRoleRequest(UserRole.USER));
assert roleResponse.getStatusCode() == HttpStatus.CONFLICT;
assert "LAST_ADMIN".equals(roleResponse.getBody());
@ -170,9 +276,10 @@ public class AuthControllerTest {
.filter(user -> user.effectiveRole() == UserRole.ADMIN)
.map(LoginEntity::getUsername)
.forEach(authRepository::deleteById);
LoginEntity admin = new LoginEntity("onlyadmin",
String username = "onlyadmin-" + uniqueSuffix();
LoginEntity admin = new LoginEntity(username,
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
"onlyadmin@example.com",
username + "@example.com",
UserRole.ADMIN);
authRepository.save(admin);
@ -181,7 +288,7 @@ public class AuthControllerTest {
null,
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
try {
ResponseEntity<?> deleteResponse = authController.deleteUser("onlyadmin");
ResponseEntity<?> deleteResponse = authController.deleteUser(username);
assert deleteResponse.getStatusCode() == HttpStatus.CONFLICT;
assert "LAST_ADMIN".equals(deleteResponse.getBody());
} finally {
@ -191,16 +298,17 @@ public class AuthControllerTest {
@Test
public void registerRejectsInvalidEmail() {
ResponseEntity<?> response = authController.register(new AuthRequest("mailuser", "Validpass1!", "not-an-email"));
ResponseEntity<?> response = authController.register(new AuthRequest("mailuser-" + uniqueSuffix(), "Validpass1!", "not-an-email"));
assert response.getStatusCode() == HttpStatus.BAD_REQUEST;
assert "INVALID_EMAIL".equals(response.getBody());
}
@Test
public void adminEndpointsAcceptJwtAdminRole() throws Exception {
LoginEntity admin = new LoginEntity("jwtadmin",
String username = "jwtadmin-" + uniqueSuffix();
LoginEntity admin = new LoginEntity(username,
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
"jwtadmin@example.com",
username + "@example.com",
UserRole.ADMIN);
authRepository.save(admin);

View File

@ -9,6 +9,7 @@ spring.jpa.properties.jakarta.persistence.validation.mode=none
app.db.init.enabled=true
app.assistant.default-model=assistant-test-model
app.mcp.bridge.url=http://localhost:18080
app.turnstile.enabled=false
app.import.path=src/test/resources/workflow-editor-init
app.import.enabled=true