diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java b/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java deleted file mode 100644 index 432daf1..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/AuthController.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.Valid; -import net.siegeln.cameleer.saas.auth.dto.AuthResponse; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/auth") -public class AuthController { - - private final AuthService authService; - - public AuthController(AuthService authService) { - this.authService = authService; - } - - @PostMapping("/register") - public ResponseEntity register(@Valid @RequestBody RegisterRequest request, - HttpServletRequest httpRequest) { - try { - var response = authService.register(request, extractClientIp(httpRequest)); - return ResponseEntity.status(HttpStatus.CREATED).body(response); - } catch (IllegalArgumentException e) { - return ResponseEntity.status(HttpStatus.CONFLICT).build(); - } - } - - @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest request, - HttpServletRequest httpRequest) { - try { - var response = authService.login(request, extractClientIp(httpRequest)); - return ResponseEntity.ok(response); - } catch (IllegalArgumentException e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - } - - private String extractClientIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isEmpty()) { - return xForwardedFor.split(",")[0].trim(); - } - return request.getRemoteAddr(); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java b/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java deleted file mode 100644 index 1df84b7..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/AuthService.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import net.siegeln.cameleer.saas.audit.AuditAction; -import net.siegeln.cameleer.saas.audit.AuditService; -import net.siegeln.cameleer.saas.auth.dto.AuthResponse; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -@Service -public class AuthService { - - private final UserRepository userRepository; - private final RoleRepository roleRepository; - private final PasswordEncoder passwordEncoder; - private final JwtService jwtService; - private final AuditService auditService; - - public AuthService(UserRepository userRepository, - RoleRepository roleRepository, - PasswordEncoder passwordEncoder, - JwtService jwtService, - AuditService auditService) { - this.userRepository = userRepository; - this.roleRepository = roleRepository; - this.passwordEncoder = passwordEncoder; - this.jwtService = jwtService; - this.auditService = auditService; - } - - public AuthResponse register(RegisterRequest request, String sourceIp) { - if (userRepository.existsByEmail(request.email())) { - throw new IllegalArgumentException("Email already registered"); - } - - var user = new UserEntity(); - user.setEmail(request.email()); - user.setName(request.name()); - user.setPassword(passwordEncoder.encode(request.password())); - - roleRepository.findByName("OWNER").ifPresent(role -> user.getRoles().add(role)); - - var saved = userRepository.save(user); - var token = jwtService.generateToken(saved); - - auditService.log( - saved.getId(), saved.getEmail(), null, - AuditAction.AUTH_REGISTER, null, - null, sourceIp, - "SUCCESS", null - ); - - return new AuthResponse(token, saved.getEmail(), saved.getName()); - } - - public AuthResponse login(LoginRequest request, String sourceIp) { - var user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); - - if (!passwordEncoder.matches(request.password(), user.getPassword())) { - auditService.log( - user.getId(), user.getEmail(), null, - AuditAction.AUTH_LOGIN_FAILED, null, - null, sourceIp, - "FAILURE", null - ); - throw new IllegalArgumentException("Invalid credentials"); - } - - var token = jwtService.generateToken(user); - - auditService.log( - user.getId(), user.getEmail(), null, - AuditAction.AUTH_LOGIN, null, - null, sourceIp, - "SUCCESS", null - ); - - return new AuthResponse(token, user.getEmail(), user.getName()); - } -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java deleted file mode 100644 index ba53b4a..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/AuthResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -public record AuthResponse( - String token, - String email, - String name -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java deleted file mode 100644 index 8602f8f..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/LoginRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -public record LoginRequest( - @NotBlank @Email String email, - @NotBlank String password -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java b/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java deleted file mode 100644 index d9e7186..0000000 --- a/src/main/java/net/siegeln/cameleer/saas/auth/dto/RegisterRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.siegeln.cameleer.saas.auth.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -public record RegisterRequest( - @NotBlank @Email String email, - @NotBlank String name, - @NotBlank @Size(min = 8, max = 128) String password -) { -} diff --git a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java index 5b19438..17c5225 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java @@ -47,7 +47,6 @@ public class SecurityConfig { .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers("/auth/verify").permitAll() .anyRequest().authenticated() diff --git a/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java b/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java index a761d8f..e414f08 100644 --- a/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java +++ b/src/main/java/net/siegeln/cameleer/saas/license/LicenseController.java @@ -32,8 +32,14 @@ public class LicenseController { var tenant = tenantService.getById(tenantId).orElse(null); if (tenant == null) return ResponseEntity.notFound().build(); - UUID actorId = authentication.getCredentials() instanceof UUID uid - ? uid : UUID.fromString(authentication.getCredentials().toString()); + // Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string) + String sub = authentication.getName(); + UUID actorId; + try { + actorId = UUID.fromString(sub); + } catch (IllegalArgumentException e) { + actorId = UUID.nameUUIDFromBytes(sub.getBytes()); + } var license = licenseService.generateLicense(tenant, Duration.ofDays(365), actorId); return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(license)); diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java index ad06203..184b88e 100644 --- a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantController.java @@ -29,9 +29,14 @@ public class TenantController { public ResponseEntity create(@Valid @RequestBody CreateTenantRequest request, Authentication authentication) { try { - // Extract actor ID from authentication credentials (Phase 1: userId stored as credentials) - UUID actorId = authentication.getCredentials() instanceof UUID uid - ? uid : UUID.fromString(authentication.getCredentials().toString()); + // Extract actor ID from JWT subject (Logto OIDC: sub may be a non-UUID string) + String sub = authentication.getName(); + UUID actorId; + try { + actorId = UUID.fromString(sub); + } catch (IllegalArgumentException e) { + actorId = UUID.nameUUIDFromBytes(sub.getBytes()); + } var entity = tenantService.create(request, actorId); return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(entity)); diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java deleted file mode 100644 index d672c07..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.siegeln.cameleer.saas.TestcontainersConfig; -import net.siegeln.cameleer.saas.TestSecurityConfig; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -@Import({TestcontainersConfig.class, TestSecurityConfig.class}) -@ActiveProfiles("test") -class AuthControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Test - void register_returns201WithToken() throws Exception { - var request = new RegisterRequest("newuser@example.com", "New User", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.token").isNotEmpty()) - .andExpect(jsonPath("$.email").value("newuser@example.com")) - .andExpect(jsonPath("$.name").value("New User")); - } - - @Test - void register_returns409ForDuplicateEmail() throws Exception { - var request = new RegisterRequest("duplicate@example.com", "User One", "password123"); - - // First registration - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()); - - // Duplicate registration - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isConflict()); - } - - @Test - void login_returns200WithToken() throws Exception { - var registerRequest = new RegisterRequest("loginuser@example.com", "Login User", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()); - - var loginRequest = new LoginRequest("loginuser@example.com", "password123"); - - mockMvc.perform(post("/api/auth/login") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(loginRequest))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token").isNotEmpty()) - .andExpect(jsonPath("$.email").value("loginuser@example.com")); - } - - @Test - void login_returns401ForBadPassword() throws Exception { - var registerRequest = new RegisterRequest("badpass@example.com", "Bad Pass", "password123"); - - mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()); - - var loginRequest = new LoginRequest("badpass@example.com", "wrong-password"); - - mockMvc.perform(post("/api/auth/login") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(loginRequest))) - .andExpect(status().isUnauthorized()); - } - - @Test - void protectedEndpoint_returns401WithoutToken() throws Exception { - mockMvc.perform(get("/api/health/secured")) - .andExpect(status().isUnauthorized()); - } - - @Test - void protectedEndpoint_returns200WithValidToken() throws Exception { - // Register to get a token - var registerRequest = new RegisterRequest("secured@example.com", "Secured User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - var responseBody = objectMapper.readTree(result.getResponse().getContentAsString()); - String token = responseBody.get("token").asText(); - - // Access protected endpoint with token - mockMvc.perform(get("/api/health/secured") - .header("Authorization", "Bearer " + token)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status").value("authenticated")); - } -} diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java deleted file mode 100644 index 3eb8282..0000000 --- a/src/test/java/net/siegeln/cameleer/saas/auth/AuthServiceTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package net.siegeln.cameleer.saas.auth; - -import net.siegeln.cameleer.saas.audit.AuditAction; -import net.siegeln.cameleer.saas.audit.AuditService; -import net.siegeln.cameleer.saas.auth.dto.LoginRequest; -import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class AuthServiceTest { - - @Mock - private UserRepository userRepository; - @Mock - private RoleRepository roleRepository; - @Mock - private PasswordEncoder passwordEncoder; - @Mock - private JwtService jwtService; - @Mock - private AuditService auditService; - - private AuthService authService; - - @BeforeEach - void setUp() { - authService = new AuthService(userRepository, roleRepository, - passwordEncoder, jwtService, auditService); - } - - @Test - void register_createsUserAndReturnsToken() { - var request = new RegisterRequest("user@example.com", "Test User", "password123"); - var ownerRole = new RoleEntity(); - ownerRole.setName("OWNER"); - - when(userRepository.existsByEmail("user@example.com")).thenReturn(false); - when(passwordEncoder.encode("password123")).thenReturn("encoded-password"); - when(roleRepository.findByName("OWNER")).thenReturn(Optional.of(ownerRole)); - when(userRepository.save(any(UserEntity.class))).thenAnswer(invocation -> { - UserEntity user = invocation.getArgument(0); - // simulate ID assignment by persistence - try { - var idField = UserEntity.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(user, java.util.UUID.randomUUID()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return user; - }); - when(jwtService.generateToken(any(UserEntity.class))).thenReturn("test-jwt-token"); - - var response = authService.register(request, "127.0.0.1"); - - assertNotNull(response); - assertEquals("test-jwt-token", response.token()); - assertEquals("user@example.com", response.email()); - assertEquals("Test User", response.name()); - - // Verify audit was logged - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_REGISTER), eq(null), - eq(null), eq("127.0.0.1"), - eq("SUCCESS"), eq(null) - ); - } - - @Test - void register_rejectsDuplicateEmail() { - var request = new RegisterRequest("existing@example.com", "Test User", "password123"); - when(userRepository.existsByEmail("existing@example.com")).thenReturn(true); - - var exception = assertThrows(IllegalArgumentException.class, - () -> authService.register(request, "127.0.0.1")); - - assertEquals("Email already registered", exception.getMessage()); - verify(userRepository, never()).save(any()); - verify(auditService, never()).log(any(), any(), any(), any(), any(), any(), any(), any(), any()); - } - - @Test - void login_returnsTokenForValidCredentials() { - var request = new LoginRequest("user@example.com", "password123"); - var user = createUserWithId("user@example.com", "encoded-password"); - - when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user)); - when(passwordEncoder.matches("password123", "encoded-password")).thenReturn(true); - when(jwtService.generateToken(user)).thenReturn("login-jwt-token"); - - var response = authService.login(request, "192.168.1.1"); - - assertNotNull(response); - assertEquals("login-jwt-token", response.token()); - assertEquals("user@example.com", response.email()); - - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_LOGIN), eq(null), - eq(null), eq("192.168.1.1"), - eq("SUCCESS"), eq(null) - ); - } - - @Test - void login_rejectsInvalidPassword() { - var request = new LoginRequest("user@example.com", "wrong-password"); - var user = createUserWithId("user@example.com", "encoded-password"); - - when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user)); - when(passwordEncoder.matches("wrong-password", "encoded-password")).thenReturn(false); - - assertThrows(IllegalArgumentException.class, - () -> authService.login(request, "192.168.1.1")); - - // Verify AUTH_LOGIN_FAILED audit was logged - verify(auditService).log( - any(), eq("user@example.com"), eq(null), - eq(AuditAction.AUTH_LOGIN_FAILED), eq(null), - eq(null), eq("192.168.1.1"), - eq("FAILURE"), eq(null) - ); - } - - @Test - void login_rejectsUnknownEmail() { - var request = new LoginRequest("unknown@example.com", "password123"); - - when(userRepository.findByEmail("unknown@example.com")).thenReturn(Optional.empty()); - - var exception = assertThrows(IllegalArgumentException.class, - () -> authService.login(request, "192.168.1.1")); - - assertEquals("Invalid credentials", exception.getMessage()); - verify(auditService, never()).log(any(), any(), any(), any(), any(), any(), any(), any(), any()); - } - - private UserEntity createUserWithId(String email, String password) { - var user = new UserEntity(); - user.setEmail(email); - user.setName("Test User"); - user.setPassword(password); - try { - var idField = UserEntity.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(user, UUID.randomUUID()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return user; - } -} diff --git a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java index b662626..124e017 100644 --- a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -30,25 +31,12 @@ class LicenseControllerTest { @Autowired private ObjectMapper objectMapper; - private String getAuthToken() throws Exception { - var registerRequest = new net.siegeln.cameleer.saas.auth.dto.RegisterRequest( - "license-test-" + System.nanoTime() + "@example.com", "Test User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - return objectMapper.readTree(result.getResponse().getContentAsString()).get("token").asText(); - } - - private String createTenantAndGetId(String token) throws Exception { + private String createTenantAndGetId() throws Exception { String slug = "license-tenant-" + System.nanoTime(); var request = new CreateTenantRequest("License Test Org", slug, "MID"); var result = mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -59,11 +47,10 @@ class LicenseControllerTest { @Test void generateLicense_returns201WithToken() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(post("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.token").isNotEmpty()) .andExpect(jsonPath("$.tier").value("MID")) @@ -72,26 +59,24 @@ class LicenseControllerTest { @Test void getActiveLicense_returnsLicense() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(post("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isCreated()); mockMvc.perform(get("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isOk()) .andExpect(jsonPath("$.tier").value("MID")); } @Test void getActiveLicense_returns404WhenNone() throws Exception { - String token = getAuthToken(); - String tenantId = createTenantAndGetId(token); + String tenantId = createTenantAndGetId(); mockMvc.perform(get("/api/tenants/" + tenantId + "/license") - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isNotFound()); } } diff --git a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java index ad36932..ebdcc58 100644 --- a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -30,26 +31,14 @@ class TenantControllerTest { @Autowired private ObjectMapper objectMapper; - private String getAuthToken() throws Exception { - var registerRequest = new net.siegeln.cameleer.saas.auth.dto.RegisterRequest( - "tenant-test-" + System.nanoTime() + "@example.com", "Test User", "password123"); - - var result = mockMvc.perform(post("/api/auth/register") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(registerRequest))) - .andExpect(status().isCreated()) - .andReturn(); - - return objectMapper.readTree(result.getResponse().getContentAsString()).get("token").asText(); - } - @Test void createTenant_returns201() throws Exception { - String token = getAuthToken(); var request = new CreateTenantRequest("Test Org", "test-org-" + System.nanoTime(), "LOW"); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j + .claim("sub", "test-user") + .claim("organization_id", "test-org"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -60,18 +49,17 @@ class TenantControllerTest { @Test void createTenant_returns409ForDuplicateSlug() throws Exception { - String token = getAuthToken(); String slug = "duplicate-slug-" + System.nanoTime(); var request = new CreateTenantRequest("First", slug, null); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()); mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isConflict()); @@ -89,12 +77,11 @@ class TenantControllerTest { @Test void getTenant_returnsTenantById() throws Exception { - String token = getAuthToken(); String slug = "get-test-" + System.nanoTime(); var request = new CreateTenantRequest("Get Test", slug, null); var createResult = mockMvc.perform(post("/api/tenants") - .header("Authorization", "Bearer " + token) + .with(jwt().jwt(j -> j.claim("sub", "test-user"))) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) @@ -103,7 +90,7 @@ class TenantControllerTest { String id = objectMapper.readTree(createResult.getResponse().getContentAsString()).get("id").asText(); mockMvc.perform(get("/api/tenants/" + id) - .header("Authorization", "Bearer " + token)) + .with(jwt().jwt(j -> j.claim("sub", "test-user")))) .andExpect(status().isOk()) .andExpect(jsonPath("$.slug").value(slug)); }