diff --git a/docker-compose.yml b/docker-compose.yml index bb817fb9..b9e690ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -255,8 +255,6 @@ services: KEYCLOAK_REALM: meldestelle KEYCLOAK_CLIENT_ID: api-gateway KEYCLOAK_CLIENT_SECRET: K5RqonwVOaxPKaXVH4mbthSRbjRh5tOK - # Custom JWT filter disabled - using oauth2ResourceServer instead - GATEWAY_SECURITY_KEYCLOAK_ENABLED: "false" ports: - "${GATEWAY_PORT:-8081}:8081" depends_on: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75ae87db..50d32966 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ resilience4j = "2.3.0" # --- Utilities --- #uuid = "0.9.0" bignum = "0.3.10" -logback = "1.5.18" +logback = "1.5.19" caffeine = "3.2.2" reactorKafka = "1.3.23" jackson = "2.19.2" diff --git a/infrastructure/auth/auth-client/build.gradle.kts b/infrastructure/auth/auth-client/build.gradle.kts deleted file mode 100644 index 8772417b..00000000 --- a/infrastructure/auth/auth-client/build.gradle.kts +++ /dev/null @@ -1,45 +0,0 @@ -// Dieses Modul enthält die clientseitige Logik für die Authentifizierung. -// Es stellt Konfigurationen und Beans bereit, um mit einem OAuth2/OIDC-Provider -// wie Keycloak zu interagieren und JWTs zu validieren. -plugins { - `java-library` - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSpring) - alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.spring.dependencyManagement) -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - -tasks.test { - useJUnitPlatform() -} - -java { - withJavadocJar() - withSourcesJar() -} - - -dependencies { - // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. - implementation(platform(projects.platform.platformBom)) - // Stellt gemeinsame Abhängigkeiten wie Coroutines und Logging bereit. - implementation(projects.platform.platformDependencies) - // Stellt Domänenobjekte und technische Utilities bereit. - implementation(projects.core.coreUtils) - // Spring Security für OAuth2-Client-Funktionalität und JWT-Verarbeitung. - implementation(libs.spring.boot.starter.oauth2.client) - implementation(libs.spring.boot.starter.security) - implementation(libs.spring.security.oauth2.jose) - // Bibliothek zur einfachen Handhabung von JWTs. - implementation(libs.auth0.java.jwt) - // JSON-Serialization für konsistente API-Datenverarbeitung. - implementation(libs.kotlinx.serialization.json) - // Stellt alle Test-Abhängigkeiten gebündelt bereit. - testImplementation(projects.platform.platformTesting) -} diff --git a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/AuthenticationService.kt b/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/AuthenticationService.kt deleted file mode 100644 index 04667b4d..00000000 --- a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/AuthenticationService.kt +++ /dev/null @@ -1,90 +0,0 @@ -@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class) -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import java.time.LocalDateTime -import kotlin.uuid.Uuid - -/** - * Service für Benutzerauthentifizierung und Passwortverwaltung. - */ -interface AuthenticationService { - /** - * Authentifiziert einen Benutzer mit Benutzernamen und Passwort. - * - * @param username Der Benutzername - * @param password Das Passwort - * @return Das Authentifizierungsergebnis - */ - suspend fun authenticate(username: String, password: String): AuthResult - - /** - * Ändert das Passwort eines Benutzers. - * - * @param userId Die Benutzer-ID - * @param currentPassword Das aktuelle Passwort - * @param newPassword Das neue Passwort - * @return Das Ergebnis der Passwortänderung - */ - suspend fun changePassword(userId: Uuid?, currentPassword: String, newPassword: String): PasswordChangeResult - - /** - * Mögliche Ergebnisse eines Authentifizierungsversuchs. - */ - sealed class AuthResult { - /** - * Authentifizierung war erfolgreich. - * - * @param token Das JWT-Token - * @param user Der authentifizierte Benutzer - */ - data class Success(val token: String, val user: AuthenticatedUser) : AuthResult() - - /** - * Authentication failed. - * - * @param reason The reason for the failure - */ - data class Failure(val reason: String) : AuthResult() - - /** - * The account is locked. - * - * @param lockedUntil The time until which the account is locked - */ - data class Locked(val lockedUntil: LocalDateTime) : AuthResult() - } - - /** - * Possible results of a password change attempt. - */ - sealed class PasswordChangeResult { - /** - * The password change was successful. - */ - data object Success : PasswordChangeResult() - - /** - * Password change failed. - * - * @param reason The reason for the failure - */ - data class Failure(val reason: String) : PasswordChangeResult() - - /** - * The new password is too weak. - */ - data object WeakPassword : PasswordChangeResult() - } - - /** - * Represents an authenticated user. - */ - data class AuthenticatedUser( - val userId: Uuid?, - val personId: Uuid?, - val username: String, - val email: String, - val permissions: List - ) -} diff --git a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/JwtService.kt b/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/JwtService.kt deleted file mode 100644 index 146a94fa..00000000 --- a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/JwtService.kt +++ /dev/null @@ -1,203 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import com.auth0.jwt.JWT -import com.auth0.jwt.algorithms.Algorithm -import com.auth0.jwt.exceptions.JWTVerificationException -import io.github.oshai.kotlinlogging.KotlinLogging -import java.nio.charset.StandardCharsets -import java.util.* -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes - -/** - * Service for JWT token generation and validation. - */ -class JwtService( - private val secret: String, - private val issuer: String, - private val audience: String, - private val expiration: Duration = 60.minutes -) { - private val logger = KotlinLogging.logger {} - - init { - require(secret.length >= 32) { "JWT secret must be at least 32 characters for HMAC512" } - require(issuer.isNotBlank()) { "JWT issuer must not be blank" } - require(audience.isNotBlank()) { "JWT audience must not be blank" } - } - - private val algorithm = Algorithm.HMAC512(secret) - private val verifier = JWT.require(algorithm) - .withIssuer(issuer) - .withAudience(audience) - .build() - - fun generateToken( - userId: String, - username: String, - permissions: List - ): String { - return JWT.create() - .withSubject(userId) - .withIssuer(issuer) - .withAudience(audience) - .withClaim("username", username) - .withArrayClaim("permissions", permissions.map { it.name }.toTypedArray()) - .withExpiresAt(Date(System.currentTimeMillis() + expiration.inWholeMilliseconds)) - .sign(algorithm) - } - - /** - * Validates a JWT token. - * - * @param token The JWT token to validate - * @return Result with true if the token is valid, or failure with error details - */ - fun validateToken(token: String): Result { - return try { - // Strict pre-check to ensure the exact Base64URL signature matches before decoding. - // This defends against edge cases where Base64URL decoders may ignore insignificant bits - // in the last character, which could allow certain tamperings to slip through. - if (!hasValidSignature(token)) { - throw JWTVerificationException("Invalid token signature") - } - - // Library verifier performs cryptographic verification and claim checks (issuer, audience, exp, ...) - verifier.verify(token) - Result.success(true) - } catch (e: JWTVerificationException) { - // Keep logging minimal to avoid timing variations under high frequency invalid inputs - logger.debug { "JWT token validation failed" } - Result.failure(e) - } catch (e: Exception) { - logger.debug { "Unexpected error during JWT token validation" } - Result.failure(e) - } - } - - /** - * Validates a JWT token (legacy method for backward compatibility). - * - * @param token The JWT token to validate - * @return True if the token is valid, false otherwise - */ - @Deprecated("Use validateToken(token: String): Result instead", ReplaceWith("validateToken(token).isSuccess")) - fun isValidToken(token: String): Boolean { - return validateToken(token).isSuccess - } - - /** - * Gets the user ID from a JWT token. - * - * @param token The JWT token - * @return Result with the user ID, or failure with error details - */ - fun getUserIdFromToken(token: String): Result { - return try { - val subject = verifier.verify(token).subject - if (subject.isNullOrBlank()) { - logger.warn { "JWT token has no subject (user ID)" } - Result.failure(IllegalStateException("JWT token has no subject")) - } else { - logger.debug { "Successfully extracted user ID from JWT token" } - Result.success(subject) - } - } catch (e: JWTVerificationException) { - logger.warn { "Failed to extract user ID from JWT token: ${e.message}" } - Result.failure(e) - } catch (e: Exception) { - logger.error(e) { "Unexpected error while extracting user ID from JWT token" } - Result.failure(e) - } - } - - /** - * Gets the user ID from a JWT token (legacy method for backward compatibility). - * - * @param token The JWT token - * @return The user ID, or null if the token is invalid - */ - @Deprecated("Use getUserIdFromToken(token: String): Result instead", ReplaceWith("getUserIdFromToken(token).getOrNull()")) - fun getUserId(token: String): String? { - return getUserIdFromToken(token).getOrNull() - } - - /** - * Gets the permissions from a JWT token. - * - * @param token The JWT token - * @return Result with the permissions, or failure with error details - */ - fun getPermissionsFromToken(token: String): Result> { - return try { - val decodedJWT = verifier.verify(token) - val permissionStrings = decodedJWT.getClaim("permissions").asArray(String::class.java) - val permissions = permissionStrings?.mapNotNull { permissionString -> - try { - BerechtigungE.valueOf(permissionString) - } catch (_: IllegalArgumentException) { - logger.warn { "Unknown permission in JWT token: $permissionString" } - null - } - } ?: emptyList() - - logger.debug { "Successfully extracted ${permissions.size} permissions from JWT token" } - Result.success(permissions) - } catch (e: JWTVerificationException) { - logger.warn { "Failed to extract permissions from JWT token: ${e.message}" } - Result.failure(e) - } catch (e: Exception) { - logger.error(e) { "Unexpected error while extracting permissions from JWT token" } - Result.failure(e) - } - } - - /** - * Gets the permissions from a JWT token (legacy method for backward compatibility). - * - * @param token The JWT token - * @return The permissions, or an empty list if the token is invalid - */ - @Deprecated("Use getPermissionsFromToken(token: String): Result> instead", ReplaceWith("getPermissionsFromToken(token).getOrElse { emptyList() }")) - fun getPermissions(token: String): List { - return getPermissionsFromToken(token).getOrElse { emptyList() } - } - - // ====== Internal helpers for strict signature validation ====== - private fun hasValidSignature(token: String): Boolean { - return try { - val parts = token.split('.') - if (parts.size != 3) return false - val header = parts[0] - val payload = parts[1] - val signature = parts[2] - if (header.isBlank() || payload.isBlank() || signature.isBlank()) return false - - val mac = Mac.getInstance("HmacSHA512") - mac.init(SecretKeySpec(secret.toByteArray(StandardCharsets.UTF_8), "HmacSHA512")) - val signingInput = "$header.$payload".toByteArray(StandardCharsets.UTF_8) - val expected = mac.doFinal(signingInput) - val expectedB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(expected) - - constantTimeEquals(expectedB64, signature) - } catch (_: Exception) { - false - } - } - - private fun constantTimeEquals(a: String, b: String): Boolean { - val aBytes = a.toByteArray(StandardCharsets.UTF_8) - val bBytes = b.toByteArray(StandardCharsets.UTF_8) - var diff = aBytes.size xor bBytes.size - val minLen = if (aBytes.size < bBytes.size) aBytes.size else bBytes.size - var i = 0 - while (i < minLen) { - diff = diff or (aBytes[i].toInt() xor bBytes[i].toInt()) - i++ - } - return diff == 0 - } -} diff --git a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/model/AuthEnums.kt b/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/model/AuthEnums.kt deleted file mode 100644 index 30351e26..00000000 --- a/infrastructure/auth/auth-client/src/main/kotlin/at/mocode/infrastructure/auth/client/model/AuthEnums.kt +++ /dev/null @@ -1,49 +0,0 @@ -package at.mocode.infrastructure.auth.client.model - -import kotlinx.serialization.Serializable - -/** - * User role enumeration for member management - */ -@Serializable -enum class RolleE { - ADMIN, // System administrator - VEREINS_ADMIN, // Club administrator - FUNKTIONAER, // Official/functionary - REITER, // Rider - TRAINER, // Trainer - RICHTER, // Judge - TIERARZT, // Veterinarian - ZUSCHAUER, // Spectator - GAST // Guest -} - -/** - * Permission enumeration for access control - */ -@Serializable -enum class BerechtigungE { - // Person management - PERSON_READ, - PERSON_CREATE, - PERSON_UPDATE, - PERSON_DELETE, - - // Club management - VEREIN_READ, - VEREIN_CREATE, - VEREIN_UPDATE, - VEREIN_DELETE, - - // Event management - VERANSTALTUNG_READ, - VERANSTALTUNG_CREATE, - VERANSTALTUNG_UPDATE, - VERANSTALTUNG_DELETE, - - // Horse management - PFERD_READ, - PFERD_CREATE, - PFERD_UPDATE, - PFERD_DELETE -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthPerformanceTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthPerformanceTest.kt deleted file mode 100644 index b3487772..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthPerformanceTest.kt +++ /dev/null @@ -1,399 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Tag -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertTimeoutPreemptively -import org.springframework.test.annotation.DirtiesContext -import java.time.Duration -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import kotlin.system.measureTimeMillis -import kotlin.time.Duration.Companion.minutes - -/** - * Performance tests for authentication operations. - * These tests ensure that JWT operations meet performance requirements under various load conditions. - */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -class AuthPerformanceTest { - - private lateinit var jwtService: JwtService - private val testSecret = "a-very-long-and-secure-test-secret-that-is-at-least-512-bits-long-for-hmac512" - private val testIssuer = "test-issuer" - private val testAudience = "test-audience" - - @BeforeEach - fun setUp() { - jwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.minutes - ) - } - - // ========== JWT Validation Performance Tests ========== - - @Test - fun `JWT validation should complete under 50ms`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - - // Act & Assert - Single validation should be reasonably fast - repeat(100) { - val timeMs = measureTimeMillis { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - assertTrue(timeMs < 50, "JWT validation should complete under 50ms (took ${timeMs}ms)") - } - } - - @Test - fun `JWT validation should handle burst load efficiently`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - val iterations = 10000 - - // Act - val timeMs = measureTimeMillis { - repeat(iterations) { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - } - - // Assert - 10,000 validations should complete within reasonable time - val avgTimeMs = timeMs.toDouble() / iterations - assertTrue(timeMs < 5000, "10,000 validations should complete within 5 seconds (took ${timeMs}ms)") - assertTrue(avgTimeMs < 0.5, "Average validation time should be under 0.5ms (was ${avgTimeMs}ms)") - } - - @Test - @Disabled("Test too flaky - JVM warmup and system load cause high variance making it unsuitable for CI") - fun `JWT validation performance should be consistent`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - val measurements = mutableListOf() - - // Act - Measure multiple batches - repeat(10) { - val batchTime = measureTimeMillis { - repeat(1000) { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - } - measurements.add(batchTime) - } - - // Assert - Performance should be consistent across batches - val avgTime = measurements.average() - val maxDeviation = measurements.maxOf { kotlin.math.abs(it - avgTime) } - assertTrue(maxDeviation < avgTime * 2.5, - "Performance should be consistent (max deviation: ${maxDeviation}ms, avg: ${avgTime}ms, tolerance: 250%)") - } - - // ========== Token Generation Performance Tests ========== - - @Tag("perf") - @Test - fun `token generation should complete under 5ms`() { - // Arrange - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE, BerechtigungE.VEREIN_UPDATE) - - // Warmup to stabilize JIT/caches - repeat(3) { - val t = jwtService.generateToken("warm-$it", "warmuser$it", permissions) - assertTrue(t.isNotEmpty()) - } - - // Act & Assert - repeat(100) { - val timeMs = measureTimeMillis { - val token = jwtService.generateToken("user-$it", "testuser$it", permissions) - assertNotNull(token) - assertTrue(token.isNotEmpty()) - } - assertTrue(timeMs < 80, "Token generation should complete under 80ms (took ${timeMs}ms)") - } - } - - @Test - fun `token generation should handle high throughput`() { - // Arrange - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.VEREIN_READ) - val iterations = 5000 - - // Act - val timeMs = measureTimeMillis { - repeat(iterations) { - val token = jwtService.generateToken("user-$it", "testuser$it", permissions) - assertTrue(token.isNotEmpty()) - } - } - - // Assert - Should generate 5000 tokens within a reasonable time - val tokensPerSecond = (iterations * 1000.0) / timeMs - assertTrue(tokensPerSecond > 1000, - "Should generate at least 1000 tokens/second (achieved ${tokensPerSecond.toInt()}/second)") - } - - // ========== Concurrent Access Performance Tests ========== - - @Test - fun `token generation should handle concurrent requests`() { - // Arrange - val threadCount = 10 - val operationsPerThread = 500 - val executor = Executors.newFixedThreadPool(threadCount) - val latch = CountDownLatch(threadCount) - val results = mutableListOf() - val errors = mutableListOf() - - // Act - val totalTime = measureTimeMillis { - repeat(threadCount) { threadIndex -> - executor.submit { - try { - repeat(operationsPerThread) { opIndex -> - val token = jwtService.generateToken( - "user-$threadIndex-$opIndex", - "testuser$threadIndex", - listOf(BerechtigungE.PERSON_READ) - ) - val isValid = jwtService.validateToken(token).isSuccess - synchronized(results) { - results.add(isValid) - } - } - } catch (e: Exception) { - synchronized(errors) { - errors.add(e) - } - } finally { - latch.countDown() - } - } - } - assertTrue(latch.await(30, TimeUnit.SECONDS), "All threads should complete within 30 seconds") - } - - executor.shutdown() - - // Assert - assertTrue(errors.isEmpty(), "No errors should occur during concurrent operations: ${errors.firstOrNull()}") - assertEquals(threadCount * operationsPerThread, results.size) - assertTrue(results.all { it }, "All tokens should be valid") - - val operationsPerSecond = (threadCount * operationsPerThread * 1000.0) / totalTime - assertTrue(operationsPerSecond > 500, - "Should handle at least 500 operations/second under concurrent load (achieved ${operationsPerSecond.toInt()}/second)") - } - - @Test - fun `token validation should handle concurrent requests`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - val threadCount = 20 - val validationsPerThread = 1000 - val executor = Executors.newFixedThreadPool(threadCount) - val latch = CountDownLatch(threadCount) - val results = mutableListOf() - - // Act - val totalTime = measureTimeMillis { - repeat(threadCount) { - executor.submit { - repeat(validationsPerThread) { - val isValid = jwtService.validateToken(token).isSuccess - synchronized(results) { - results.add(isValid) - } - } - latch.countDown() - } - } - assertTrue(latch.await(10, TimeUnit.SECONDS), "All validations should complete within 10 seconds") - } - - executor.shutdown() - - // Assert - assertEquals(threadCount * validationsPerThread, results.size) - assertTrue(results.all { it }, "All validations should succeed") - - val validationsPerSecond = (threadCount * validationsPerThread * 1000.0) / totalTime - assertTrue(validationsPerSecond > 10000, - "Should handle at least 10,000 validations/second under concurrent load (achieved ${validationsPerSecond.toInt()}/second)") - } - - // ========== Memory Usage Performance Tests ========== - - @Test - fun `memory usage should be stable under load`() { - // Arrange - val runtime = Runtime.getRuntime() - val initialMemory = runtime.totalMemory() - runtime.freeMemory() - - // Act - Perform many operations to test for memory leaks - repeat(10000) { - val token = jwtService.generateToken("user-$it", "testuser$it", listOf(BerechtigungE.PERSON_READ)) - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - - // Extract data to ensure full processing - jwtService.getUserIdFromToken(token) - jwtService.getPermissionsFromToken(token) - } - - // Force garbage collection - System.gc() - Thread.sleep(100) // Give GC time to run - - val finalMemory = runtime.totalMemory() - runtime.freeMemory() - val memoryIncrease = finalMemory - initialMemory - - // Assert - Memory increase should be reasonable (less than 50MB) - assertTrue(memoryIncrease < 50 * 1024 * 1024, - "Memory increase should be less than 50MB (was ${memoryIncrease / 1024 / 1024}MB)") - } - - // ========== Complex Permissions Performance Tests ========== - - @Tag("perf") - @Test - fun `should handle large permission sets efficiently`() { - // Arrange - Create a token with all available permissions - val allPermissions = BerechtigungE.entries - - // Warmup - allow JIT and caches to stabilize - repeat(3) { - val warmToken = jwtService.generateToken("admin-user", "admin", allPermissions) - jwtService.validateToken(warmToken) - jwtService.getPermissionsFromToken(warmToken) - } - - // Act & Assert - Generation should still be fast - val generationTime = measureTimeMillis { - val token = jwtService.generateToken("admin-user", "admin", allPermissions) - assertNotNull(token) - } - assertTrue(generationTime < 500, "Generation with all permissions should be under 500ms (was ${generationTime}ms)") - - // Validation should also be fast - val token = jwtService.generateToken("admin-user", "admin", allPermissions) - val validationTime = measureTimeMillis { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - - val permissions = jwtService.getPermissionsFromToken(token).getOrElse { emptyList() } - assertEquals(allPermissions.size, permissions.size) - } - assertTrue(validationTime < 120, "Validation with all permissions should be under 120ms (was ${validationTime}ms)") - } - - // ========== Stress Tests ========== - - @Test - fun `should handle sustained load without degradation`() { - // Arrange - val testDurationMs = 5000L // 5 seconds - val startTime = System.currentTimeMillis() - var operationCount = 0 - val measurementPoints = mutableListOf>() // time, operations per second - - // Act - Sustained load test - while (System.currentTimeMillis() - startTime < testDurationMs) { - val intervalStart = System.currentTimeMillis() - var intervalOperations = 0 - - // Run operations for 1-second intervals - while (System.currentTimeMillis() - intervalStart < 1000) { - val token = jwtService.generateToken("user-$operationCount", "test", listOf(BerechtigungE.PERSON_READ)) - val isValid = jwtService.validateToken(token).isSuccess - assertTrue(isValid) - operationCount++ - intervalOperations++ - } - - measurementPoints.add(Pair(System.currentTimeMillis() - startTime, intervalOperations)) - } - - // Assert - Performance should not degrade significantly over time - assertTrue(measurementPoints.size >= 4, "Should have at least 4 measurement points") - - val firstHalf = measurementPoints.take(measurementPoints.size / 2).map { it.second } - val secondHalf = measurementPoints.drop(measurementPoints.size / 2).map { it.second } - - val firstHalfAvg = firstHalf.average() - val secondHalfAvg = secondHalf.average() - - // Performance in the second half should not be significantly worse than the first half - assertTrue(secondHalfAvg > firstHalfAvg * 0.8, - "Performance should not degrade by more than 20% over time " + - "(first half: ${firstHalfAvg.toInt()} ops/sec, second half: ${secondHalfAvg.toInt()} ops/sec)") - } - - @Test - fun `operations should complete within timeout under extreme load`() { - // Arrange - Very high-load scenario - val operations = 50000 - - // Act & Assert - Should complete within a reasonable timeout - assertTimeoutPreemptively(Duration.ofSeconds(30)) { - repeat(operations) { - val token = jwtService.generateToken("user-$it", "test", listOf(BerechtigungE.PERSON_READ)) - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - } - } - - // ========== Benchmarking Tests ========== - - @Test - fun `benchmark basic JWT operations`() { - // This test provides baseline performance metrics for monitoring - val iterations = 1000 - - // Token Generation Benchmark - val generationTime = measureTimeMillis { - repeat(iterations) { - jwtService.generateToken("user-$it", "test", listOf(BerechtigungE.PERSON_READ)) - } - } - val avgGenerationMs = generationTime.toDouble() / iterations - println("[DEBUG_LOG] Token generation: ${avgGenerationMs}ms average (${iterations} iterations)") - - // Token Validation Benchmark - val token = jwtService.generateToken("benchmark-user", "test", listOf(BerechtigungE.PERSON_READ)) - val validationTime = measureTimeMillis { - repeat(iterations) { - jwtService.validateToken(token) - } - } - val avgValidationMs = validationTime.toDouble() / iterations - println("[DEBUG_LOG] Token validation: ${avgValidationMs}ms average (${iterations} iterations)") - - // Data Extraction Benchmark - val extractionTime = measureTimeMillis { - repeat(iterations) { - jwtService.getUserIdFromToken(token) - jwtService.getPermissionsFromToken(token) - } - } - val avgExtractionMs = extractionTime.toDouble() / iterations - println("[DEBUG_LOG] Data extraction: ${avgExtractionMs}ms average (${iterations} iterations)") - - // Performance should meet baseline requirements - assertTrue(avgGenerationMs < 2.0, "Token generation should average under 2ms") - assertTrue(avgValidationMs < 1.0, "Token validation should average under 1ms") - assertTrue(avgExtractionMs < 1.0, "Data extraction should average under 1ms") - } -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthenticationServiceTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthenticationServiceTest.kt deleted file mode 100644 index bda3881a..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/AuthenticationServiceTest.kt +++ /dev/null @@ -1,346 +0,0 @@ -@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class) // <-- HINZUGEFÜGT: Für die neue Kotlin UUID API - -package at.mocode.infrastructure.auth.client - -// import com.benasher44.uuid.uuid4 // <-- ENTFERNT: Alter Import -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import io.mockk.coEvery -import io.mockk.mockk -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.time.LocalDateTime -import kotlin.uuid.Uuid - -/** - * Tests for the AuthenticationService interface using mocks. - * These tests verify the contract and behavior expectations without requiring real implementations. - */ -class AuthenticationServiceTest { - - private lateinit var authService: AuthenticationService - private val testUserId = Uuid.random() // <-- GEÄNDERT: von uuid4() zu Uuid.random() - private val testPersonId = Uuid.random() // <-- GEÄNDERT: von uuid4() zu Uuid.random() - - @BeforeEach - fun setUp() { - authService = mockk() - } - - // ========== Authentication Tests ========== - - @Test - fun `authenticate should return Success for valid credentials`() = runTest { - // Arrange - val username = "testuser" - val password = "validpassword" - val expectedUser = AuthenticationService.AuthenticatedUser( - userId = testUserId, - personId = testPersonId, - username = username, - email = "test@example.com", - permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.VEREIN_READ) - ) - val expectedToken = "valid.jwt.token" - - coEvery { authService.authenticate(username, password) } returns - AuthenticationService.AuthResult.Success(expectedToken, expectedUser) - - // Act - val result = authService.authenticate(username, password) - - // Assert - assertTrue(result is AuthenticationService.AuthResult.Success) - val successResult = result as AuthenticationService.AuthResult.Success - assertEquals(expectedToken, successResult.token) - assertEquals(expectedUser.userId, successResult.user.userId) - assertEquals(expectedUser.username, successResult.user.username) - assertEquals(expectedUser.email, successResult.user.email) - assertEquals(2, successResult.user.permissions.size) - assertTrue(successResult.user.permissions.contains(BerechtigungE.PERSON_READ)) - assertTrue(successResult.user.permissions.contains(BerechtigungE.VEREIN_READ)) - } - - @Test - fun `authenticate should return Failure for invalid credentials`() = runTest { - // Arrange - val username = "testuser" - val password = "wrongpassword" - val expectedReason = "Invalid username or password" - - coEvery { authService.authenticate(username, password) } returns - AuthenticationService.AuthResult.Failure(expectedReason) - - // Act - val result = authService.authenticate(username, password) - - // Assert - assertTrue(result is AuthenticationService.AuthResult.Failure) - val failureResult = result as AuthenticationService.AuthResult.Failure - assertEquals(expectedReason, failureResult.reason) - } - - @Test - fun `authenticate should return Locked for locked accounts`() = runTest { - // Arrange - val username = "lockeduser" - val password = "password" - val lockedUntil = LocalDateTime.now().plusHours(1) - - coEvery { authService.authenticate(username, password) } returns - AuthenticationService.AuthResult.Locked(lockedUntil) - - // Act - val result = authService.authenticate(username, password) - - // Assert - assertTrue(result is AuthenticationService.AuthResult.Locked) - val lockedResult = result as AuthenticationService.AuthResult.Locked - assertEquals(lockedUntil, lockedResult.lockedUntil) - } - - @Test - fun `authenticate should handle empty username gracefully`() = runTest { - // Arrange - val emptyUsername = "" - val password = "password" - - coEvery { authService.authenticate(emptyUsername, password) } returns - AuthenticationService.AuthResult.Failure("Username cannot be empty") - - // Act - val result = authService.authenticate(emptyUsername, password) - - // Assert - assertTrue(result is AuthenticationService.AuthResult.Failure) - val failureResult = result as AuthenticationService.AuthResult.Failure - assertTrue(failureResult.reason.contains("Username")) - } - - @Test - fun `authenticate should handle empty password gracefully`() = runTest { - // Arrange - val username = "testuser" - val emptyPassword = "" - - coEvery { authService.authenticate(username, emptyPassword) } returns - AuthenticationService.AuthResult.Failure("Password cannot be empty") - - // Act - val result = authService.authenticate(username, emptyPassword) - - // Assert - assertTrue(result is AuthenticationService.AuthResult.Failure) - val failureResult = result as AuthenticationService.AuthResult.Failure - assertTrue(failureResult.reason.contains("Password")) - } - - // ========== Password Change Tests ========== - - @Test - fun `changePassword should return Success for valid password change`() = runTest { - // Arrange - val currentPassword = "oldpassword" - val newPassword = "newpassword123" - - coEvery { authService.changePassword(testUserId, currentPassword, newPassword) } returns - AuthenticationService.PasswordChangeResult.Success - - // Act - val result = authService.changePassword(testUserId, currentPassword, newPassword) - - // Assert - assertTrue(result is AuthenticationService.PasswordChangeResult.Success) - } - - @Test - fun `changePassword should validate current password`() = runTest { - // Arrange - val wrongCurrentPassword = "wrongpassword" - val newPassword = "newpassword123" - - coEvery { authService.changePassword(testUserId, wrongCurrentPassword, newPassword) } returns - AuthenticationService.PasswordChangeResult.Failure("Current password is incorrect") - - // Act - val result = authService.changePassword(testUserId, wrongCurrentPassword, newPassword) - - // Assert - assertTrue(result is AuthenticationService.PasswordChangeResult.Failure) - val failureResult = result as AuthenticationService.PasswordChangeResult.Failure - assertTrue(failureResult.reason.contains("Current password")) - } - - @Test - fun `changePassword should reject weak passwords`() = runTest { - // Arrange - val currentPassword = "oldpassword" - val weakPassword = "123" // Too short and simple - - coEvery { authService.changePassword(testUserId, currentPassword, weakPassword) } returns - AuthenticationService.PasswordChangeResult.WeakPassword - - // Act - val result = authService.changePassword(testUserId, currentPassword, weakPassword) - - // Assert - assertTrue(result is AuthenticationService.PasswordChangeResult.WeakPassword) - } - - @Test - fun `changePassword should handle concurrent modifications`() = runTest { - // Arrange - val currentPassword = "oldpassword" - val newPassword = "newpassword123" - - coEvery { authService.changePassword(testUserId, currentPassword, newPassword) } returns - AuthenticationService.PasswordChangeResult.Failure("User was modified concurrently") - - // Act - val result = authService.changePassword(testUserId, currentPassword, newPassword) - - // Assert - assertTrue(result is AuthenticationService.PasswordChangeResult.Failure) - val failureResult = result as AuthenticationService.PasswordChangeResult.Failure - assertTrue(failureResult.reason.contains("concurrently")) - } - - @Test - fun `changePassword should handle user not found scenario`() = runTest { - // Arrange - val nonExistentUserId = Uuid.random() // <-- GEÄNDERT: von uuid4() zu Uuid.random() - val currentPassword = "password" - val newPassword = "newpassword123" - - coEvery { authService.changePassword(nonExistentUserId, currentPassword, newPassword) } returns - AuthenticationService.PasswordChangeResult.Failure("User not found") - - // Act - val result = authService.changePassword(nonExistentUserId, currentPassword, newPassword) - - // Assert - assertTrue(result is AuthenticationService.PasswordChangeResult.Failure) - val failureResult = result as AuthenticationService.PasswordChangeResult.Failure - assertTrue(failureResult.reason.contains("not found")) - } - - // ========== AuthenticatedUser Model Tests ========== - - @Test - fun `AuthenticatedUser should properly encapsulate user data`() { - // Arrange & Act - val user = AuthenticationService.AuthenticatedUser( - userId = testUserId, - personId = testPersonId, - username = "testuser", - email = "test@example.com", - permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE) - ) - - // Assert - assertEquals(testUserId, user.userId) - assertEquals(testPersonId, user.personId) - assertEquals("testuser", user.username) - assertEquals("test@example.com", user.email) - assertEquals(2, user.permissions.size) - assertTrue(user.permissions.contains(BerechtigungE.PERSON_READ)) - assertTrue(user.permissions.contains(BerechtigungE.PFERD_CREATE)) - } - - @Test - fun `AuthenticatedUser should handle empty permissions list`() { - // Arrange & Act - val user = AuthenticationService.AuthenticatedUser( - userId = testUserId, - personId = testPersonId, - username = "limiteduser", - email = "limited@example.com", - permissions = emptyList() - ) - - // Assert - assertTrue(user.permissions.isEmpty()) - assertEquals("limiteduser", user.username) - } - - // ========== Result Type Pattern Tests ========== - - @Test - fun `AuthResult sealed class should support pattern matching`() = runTest { - // Arrange - val successResult = AuthenticationService.AuthResult.Success( - "token", - AuthenticationService.AuthenticatedUser( - testUserId, testPersonId, "user", "email@test.com", emptyList() - ) - ) - val failureResult = AuthenticationService.AuthResult.Failure("Failed") - val lockedResult = AuthenticationService.AuthResult.Locked(LocalDateTime.now()) - - // Act & Assert - // Test Success result - when (successResult) { - is AuthenticationService.AuthResult.Success -> { - assertNotNull(successResult.token) - assertNotNull(successResult.user) - } - - else -> fail("Should have been a Success result") - } - - // Test Failure result - when (failureResult) { - is AuthenticationService.AuthResult.Failure -> { - assertEquals("Failed", failureResult.reason) - } - - else -> fail("Should have been a Failure result") - } - - // Test Locked result - when (lockedResult) { - is AuthenticationService.AuthResult.Locked -> { - assertNotNull(lockedResult.lockedUntil) - } - - else -> fail("Should have been a Locked result") - } - } - - @Test - fun `PasswordChangeResult sealed class should support pattern matching`() = runTest { - // Arrange - val successResult = AuthenticationService.PasswordChangeResult.Success - val failureResult = AuthenticationService.PasswordChangeResult.Failure("Failed") - val weakPasswordResult = AuthenticationService.PasswordChangeResult.WeakPassword - - // Act & Assert - // Test Success result - when (successResult) { - is AuthenticationService.PasswordChangeResult.Success -> { - // Success case verified - } - - else -> fail("Should have been a Success result") - } - - // Test Failure result - when (failureResult) { - is AuthenticationService.PasswordChangeResult.Failure -> { - assertEquals("Failed", failureResult.reason) - } - - else -> fail("Should have been a Failure result") - } - - // Test WeakPassword result - when (weakPasswordResult) { - is AuthenticationService.PasswordChangeResult.WeakPassword -> { - // WeakPassword case verified - } - - else -> fail("Should have been a WeakPassword result") - } - } -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceExtendedTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceExtendedTest.kt deleted file mode 100644 index e83e2c09..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceExtendedTest.kt +++ /dev/null @@ -1,299 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import com.auth0.jwt.exceptions.JWTVerificationException -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -/** - * Extended tests for JwtService focusing on Result-based APIs, edge cases, and security scenarios. - */ -class JwtServiceExtendedTest { - - private lateinit var jwtService: JwtService - private val testSecret = "a-very-long-and-secure-test-secret-that-is-at-least-512-bits-long-for-hmac512" - private val testIssuer = "test-issuer" - private val testAudience = "test-audience" - - @BeforeEach - fun setUp() { - jwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.minutes - ) - } - - // ========== Result API Tests ========== - - @Test - fun `validateToken should return Success with true for valid token`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - - // Act - val result = jwtService.validateToken(token) - - // Assert - assertTrue(result.isSuccess) - assertEquals(true, result.getOrNull()) - } - - @Test - fun `validateToken should return Failure for malformed token`() { - // Arrange - val malformedToken = "this.is.not.a.valid.jwt.token" - - // Act - val result = jwtService.validateToken(malformedToken) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `validateToken should return Failure for token with wrong issuer`() { - // Arrange - val wrongIssuerService = JwtService(testSecret, "wrong-issuer", testAudience) - val token = wrongIssuerService.generateToken("user-123", "test", emptyList()) - - // Act - val result = jwtService.validateToken(token) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `validateToken should return Failure for token with wrong audience`() { - // Arrange - val wrongAudienceService = JwtService(testSecret, testIssuer, "wrong-audience") - val token = wrongAudienceService.generateToken("user-123", "test", emptyList()) - - // Act - val result = jwtService.validateToken(token) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `validateToken should return Failure for expired token`() { - // Arrange - val expiredService = JwtService(testSecret, testIssuer, testAudience, expiration = (-10).seconds) - val token = expiredService.generateToken("user-123", "test", emptyList()) - - // Act - val result = jwtService.validateToken(token) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - // ========== getUserIdFromToken Result API Tests ========== - - @Test - fun `getUserIdFromToken should return Success with user ID for valid token`() { - // Arrange - val userId = "user-12345" - val token = jwtService.generateToken(userId, "testuser", emptyList()) - - // Act - val result = jwtService.getUserIdFromToken(token) - - // Assert - assertTrue(result.isSuccess) - assertEquals(userId, result.getOrNull()) - } - - @Test - fun `getUserIdFromToken should return Failure for invalid token`() { - // Arrange - val invalidToken = "invalid.jwt.token" - - // Act - val result = jwtService.getUserIdFromToken(invalidToken) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `getUserIdFromToken should handle missing subject claim`() { - // Note: This test verifies that empty/blank subject claims are properly rejected for security - val token = jwtService.generateToken("", "testuser", emptyList()) - - val result = jwtService.getUserIdFromToken(token) - - // Empty subject should be rejected for security reasons - assertTrue(result.isFailure) - assertInstanceOf(IllegalStateException::class.java, result.exceptionOrNull()) - assertTrue(result.exceptionOrNull()!!.message!!.contains("no subject")) - } - - // ========== getPermissionsFromToken Result API Tests ========== - - @Test - fun `getPermissionsFromToken should return Success with permissions for valid token`() { - // Arrange - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE, BerechtigungE.VEREIN_UPDATE) - val token = jwtService.generateToken("user-123", "testuser", permissions) - - // Act - val result = jwtService.getPermissionsFromToken(token) - - // Assert - assertTrue(result.isSuccess) - val extractedPermissions = result.getOrNull()!! - assertEquals(3, extractedPermissions.size) - assertTrue(extractedPermissions.containsAll(permissions)) - } - - @Test - fun `getPermissionsFromToken should return Failure for invalid token`() { - // Arrange - val invalidToken = "invalid.jwt.token" - - // Act - val result = jwtService.getPermissionsFromToken(invalidToken) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `getPermissionsFromToken should return empty list for token without permissions`() { - // Arrange - val token = jwtService.generateToken("user-123", "testuser", emptyList()) - - // Act - val result = jwtService.getPermissionsFromToken(token) - - // Assert - assertTrue(result.isSuccess) - val permissions = result.getOrNull()!! - assertTrue(permissions.isEmpty()) - } - - @Test - fun `getPermissionsFromToken should ignore unknown permissions gracefully`() { - // This test simulates a token with permissions that don't exist in the enum - // In practice, this would require manually crafting a JWT, so this tests the enum parsing logic - val permissions = listOf(BerechtigungE.PERSON_READ) - val token = jwtService.generateToken("user-123", "testuser", permissions) - - val result = jwtService.getPermissionsFromToken(token) - - assertTrue(result.isSuccess) - val extractedPermissions = result.getOrNull()!! - assertEquals(1, extractedPermissions.size) - assertEquals(BerechtigungE.PERSON_READ, extractedPermissions[0]) - } - - // ========== Token Generation Tests ========== - - @Test - fun `generateToken should create tokens with correct expiration time`() { - // Arrange - val shortExpirationService = JwtService(testSecret, testIssuer, testAudience, expiration = 5.seconds) - val token = shortExpirationService.generateToken("user-123", "test", emptyList()) - - // Act - Validate immediately (should be valid) - val immediateResult = shortExpirationService.validateToken(token) - - // Wait and validate again (should be expired) - using Thread.sleep is acceptable for this specific test - Thread.sleep(6000) // 6 seconds - val delayedResult = shortExpirationService.validateToken(token) - - // Assert - assertTrue(immediateResult.isSuccess, "Token should be valid immediately after creation") - assertTrue(delayedResult.isFailure, "Token should be expired after waiting") - } - - // ========== Legacy Method Backward Compatibility Tests ========== - - @Test - fun `legacy methods should maintain backward compatibility`() { - // Arrange - val userId = "user-123" - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE) - val token = jwtService.generateToken(userId, "testuser", permissions) - - // Act & Assert - Legacy methods should still work - @Suppress("DEPRECATION") - assertTrue(jwtService.isValidToken(token)) - - @Suppress("DEPRECATION") - assertEquals(userId, jwtService.getUserId(token)) - - @Suppress("DEPRECATION") - val legacyPermissions = jwtService.getPermissions(token) - assertEquals(2, legacyPermissions.size) - assertTrue(legacyPermissions.containsAll(permissions)) - } - - @Test - fun `legacy methods should handle invalid tokens gracefully`() { - // Arrange - val invalidToken = "invalid.token" - - // Act & Assert - Legacy methods should handle errors gracefully - @Suppress("DEPRECATION") - assertFalse(jwtService.isValidToken(invalidToken)) - - @Suppress("DEPRECATION") - assertNull(jwtService.getUserId(invalidToken)) - - @Suppress("DEPRECATION") - val permissions = jwtService.getPermissions(invalidToken) - assertTrue(permissions.isEmpty()) - } - - // ========== Security Edge Cases ========== - - @Test - fun `should reject tokens with tampered signatures`() { - // Arrange - val validToken = jwtService.generateToken("user-123", "testuser", emptyList()) - val tamperedToken = validToken.dropLast(5) + "TAMPR" - - // Act - val result = jwtService.validateToken(tamperedToken) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `should handle empty token gracefully`() { - // Act - val result = jwtService.validateToken("") - - // Assert - assertTrue(result.isFailure) - assertNotNull(result.exceptionOrNull()) - } - - @Test - fun `should handle null-like values in token validation`() { - // Arrange - val nullLikeTokens = listOf("null", "undefined", " ", "\t", "\n") - - // Act & Assert - nullLikeTokens.forEach { token -> - val result = jwtService.validateToken(token) - assertTrue(result.isFailure, "Token '$token' should be rejected") - } - } -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceTest.kt deleted file mode 100644 index eaf60811..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/JwtServiceTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import kotlin.time.Duration.Companion.seconds - -class JwtServiceTest { - - private lateinit var jwtService: JwtService - private val testSecret = "a-very-long-and-secure-test-secret-that-is-at-least-512-bits-long-for-hmac512" - private val testIssuer = "test-issuer" - private val testAudience = "test-audience" - - @BeforeEach - fun setUp() { - jwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.seconds // Kurze Lebensdauer für Tests - ) - } - - @Test - fun `generateToken should create a valid JWT with correct claims`() { - // Arrange - val userId = "user-123" - val username = "testuser" - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE) - - // Act - val token = jwtService.generateToken(userId, username, permissions) - - // Assert - assertNotNull(token) - assertTrue(jwtService.validateToken(token).isSuccess) - assertEquals(userId, jwtService.getUserIdFromToken(token).getOrNull()) - - val extractedPermissions = jwtService.getPermissionsFromToken(token).getOrElse { emptyList() } - assertEquals(2, extractedPermissions.size) - assertTrue(extractedPermissions.contains(BerechtigungE.PERSON_READ)) - assertTrue(extractedPermissions.contains(BerechtigungE.PFERD_CREATE)) - } - - @Test - fun `validateToken should return false for token with wrong secret`() { - // Arrange - val otherService = JwtService("a-different-wrong-secret-that-is-long-enough-1234567890", testIssuer, testAudience) - val token = otherService.generateToken("user-123", "test", emptyList()) - - // Act & Assert - assertFalse(jwtService.validateToken(token).isSuccess) - } - - @Test - fun `validateToken should return false for expired token`() { - // Arrange - val expiredService = - JwtService(testSecret, testIssuer, testAudience, expiration = (-10).seconds) // bereits abgelaufen - val token = expiredService.generateToken("user-123", "test", emptyList()) - - // Act & Assert - assertFalse(jwtService.validateToken(token).isSuccess) - } - - @Test - fun `getPermissionsFromToken should return empty list for invalid token`() { - // Arrange - val invalidToken = "this.is.not.a.valid.token" - - // Act - val permissions = jwtService.getPermissionsFromToken(invalidToken).getOrElse { emptyList() } - - // Assert - assertTrue(permissions.isEmpty()) - } -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/ResultApiTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/ResultApiTest.kt deleted file mode 100644 index 36408c7a..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/ResultApiTest.kt +++ /dev/null @@ -1,331 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import com.auth0.jwt.exceptions.JWTVerificationException -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -/** - * Comprehensive tests for the Result-based APIs in the auth module. - * Tests focus on Result type behavior, error handling, and API consistency. - */ -class ResultApiTest { - - private lateinit var jwtService: JwtService - private val testSecret = "a-very-long-and-secure-test-secret-that-is-at-least-512-bits-long-for-hmac512" - private val testIssuer = "test-issuer" - private val testAudience = "test-audience" - - @BeforeEach - fun setUp() { - jwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.minutes - ) - } - - // ========== Result Success Cases Tests ========== - - @Test - fun `Result success cases should provide correct values`() { - // Arrange - val userId = "user-12345" - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE) - val token = jwtService.generateToken(userId, "testuser", permissions) - - // Act - Test all Result-based APIs - val validateResult = jwtService.validateToken(token) - val userIdResult = jwtService.getUserIdFromToken(token) - val permissionsResult = jwtService.getPermissionsFromToken(token) - - // Assert - All should be successful - assertTrue(validateResult.isSuccess) - assertTrue(validateResult.isFailure.not()) - assertEquals(true, validateResult.getOrNull()) - assertNull(validateResult.exceptionOrNull()) - - assertTrue(userIdResult.isSuccess) - assertTrue(userIdResult.isFailure.not()) - assertEquals(userId, userIdResult.getOrNull()) - assertNull(userIdResult.exceptionOrNull()) - - assertTrue(permissionsResult.isSuccess) - assertTrue(permissionsResult.isFailure.not()) - val extractedPermissions = permissionsResult.getOrNull()!! - assertEquals(2, extractedPermissions.size) - assertTrue(extractedPermissions.containsAll(permissions)) - assertNull(permissionsResult.exceptionOrNull()) - } - - @Test - fun `Result getOrElse should work correctly for success cases`() { - // Arrange - val token = jwtService.generateToken("user-123", "test", listOf(BerechtigungE.VEREIN_READ)) - - // Act & Assert - val isValid = jwtService.validateToken(token).getOrElse { false } - assertTrue(isValid) - - val userId = jwtService.getUserIdFromToken(token).getOrElse { "default" } - assertEquals("user-123", userId) - - val permissions = jwtService.getPermissionsFromToken(token).getOrElse { emptyList() } - assertEquals(1, permissions.size) - assertEquals(BerechtigungE.VEREIN_READ, permissions[0]) - } - - // ========== Result Failure Cases Tests ========== - - @Test - fun `Result failure cases should contain meaningful error messages`() { - // Arrange - val invalidToken = "invalid.jwt.token" - - // Act - val validateResult = jwtService.validateToken(invalidToken) - val userIdResult = jwtService.getUserIdFromToken(invalidToken) - val permissionsResult = jwtService.getPermissionsFromToken(invalidToken) - - // Assert - All should be failures with proper exception types - assertTrue(validateResult.isFailure) - assertTrue(validateResult.isSuccess.not()) - assertNull(validateResult.getOrNull()) - assertInstanceOf(JWTVerificationException::class.java, validateResult.exceptionOrNull()) - - assertTrue(userIdResult.isFailure) - assertTrue(userIdResult.isSuccess.not()) - assertNull(userIdResult.getOrNull()) - assertInstanceOf(JWTVerificationException::class.java, userIdResult.exceptionOrNull()) - - assertTrue(permissionsResult.isFailure) - assertTrue(permissionsResult.isSuccess.not()) - assertNull(permissionsResult.getOrNull()) - assertInstanceOf(JWTVerificationException::class.java, permissionsResult.exceptionOrNull()) - } - - @Test - fun `Result getOrElse should work correctly for failure cases`() { - // Arrange - val invalidToken = "invalid.token" - - // Act & Assert - val isValid = jwtService.validateToken(invalidToken).getOrElse { false } - assertFalse(isValid) - - val userId = jwtService.getUserIdFromToken(invalidToken).getOrElse { "default-user" } - assertEquals("default-user", userId) - - val permissions = jwtService.getPermissionsFromToken(invalidToken).getOrElse { emptyList() } - assertTrue(permissions.isEmpty()) - } - - @Test - fun `Result getOrDefault should handle different default types`() { - // Arrange - val invalidToken = "malformed.jwt" - - // Act & Assert - Test various default value types - val defaultBoolean = jwtService.validateToken(invalidToken).getOrElse { true } - assertTrue(defaultBoolean) - - val defaultString = jwtService.getUserIdFromToken(invalidToken).getOrElse { "anonymous" } - assertEquals("anonymous", defaultString) - - val defaultList = jwtService.getPermissionsFromToken(invalidToken).getOrElse { listOf(BerechtigungE.PERSON_READ) } - assertEquals(1, defaultList.size) - assertEquals(BerechtigungE.PERSON_READ, defaultList[0]) - } - - // ========== Result Chaining Tests ========== - - @Test - fun `Result chaining should work correctly`() { - // Arrange - val token = jwtService.generateToken("user-123", "test", listOf(BerechtigungE.PERSON_READ)) - - // Act - Chain Result operations - val chainedResult = jwtService.validateToken(token) - .map { isValid -> if (isValid) "VALID" else "INVALID" } - - val userChainedResult = jwtService.getUserIdFromToken(token) - .map { userId -> "User: $userId" } - - val permissionChainedResult = jwtService.getPermissionsFromToken(token) - .map { permissions -> permissions.map { it.name } } - - // Assert - assertTrue(chainedResult.isSuccess) - assertEquals("VALID", chainedResult.getOrNull()) - - assertTrue(userChainedResult.isSuccess) - assertEquals("User: user-123", userChainedResult.getOrNull()) - - assertTrue(permissionChainedResult.isSuccess) - val permissionNames = permissionChainedResult.getOrNull()!! - assertEquals(1, permissionNames.size) - assertEquals("PERSON_READ", permissionNames[0]) - } - - @Test - fun `Result chaining should handle failures correctly`() { - // Arrange - val invalidToken = "bad.token" - - // Act - Chain operations that will fail - val chainedResult = jwtService.validateToken(invalidToken) - .map { isValid -> "This should not be called" } - - val userChainedResult = jwtService.getUserIdFromToken(invalidToken) - .map { userId -> "User: $userId" } - - // Assert - Chained operations should not execute on failure - assertTrue(chainedResult.isFailure) - assertNull(chainedResult.getOrNull()) - - assertTrue(userChainedResult.isFailure) - assertNull(userChainedResult.getOrNull()) - } - - // ========== Exception Handling Consistency Tests ========== - - @Test - fun `Exception handling should be consistent across all Result methods`() { - // Test various types of invalid tokens - val testCases = listOf( - "malformed.token", - "", - "too.short", - "way.too.many.parts.in.this.token.structure", - "null.claims.signature" - ) - - testCases.forEach { invalidToken -> - // All methods should handle the same invalid input consistently - val validateResult = jwtService.validateToken(invalidToken) - val userIdResult = jwtService.getUserIdFromToken(invalidToken) - val permissionsResult = jwtService.getPermissionsFromToken(invalidToken) - - // All should fail - assertTrue(validateResult.isFailure, "validateToken should fail for: $invalidToken") - assertTrue(userIdResult.isFailure, "getUserIdFromToken should fail for: $invalidToken") - assertTrue(permissionsResult.isFailure, "getPermissionsFromToken should fail for: $invalidToken") - - // All should have non-null exceptions - assertNotNull(validateResult.exceptionOrNull(), "validateToken should have exception for: $invalidToken") - assertNotNull(userIdResult.exceptionOrNull(), "getUserIdFromToken should have exception for: $invalidToken") - assertNotNull(permissionsResult.exceptionOrNull(), "getPermissionsFromToken should have exception for: $invalidToken") - } - } - - // ========== Special Edge Cases for Result API ========== - - @Test - fun `Result API should handle expired tokens consistently`() { - // Arrange - val expiredService = JwtService(testSecret, testIssuer, testAudience, expiration = (-10).seconds) - val expiredToken = expiredService.generateToken("user-123", "test", listOf(BerechtigungE.PERSON_READ)) - - // Act - val validateResult = jwtService.validateToken(expiredToken) - val userIdResult = jwtService.getUserIdFromToken(expiredToken) - val permissionsResult = jwtService.getPermissionsFromToken(expiredToken) - - // Assert - All should consistently fail for expired tokens - assertTrue(validateResult.isFailure) - assertTrue(userIdResult.isFailure) - assertTrue(permissionsResult.isFailure) - - // All should have JWT verification exceptions - assertInstanceOf(JWTVerificationException::class.java, validateResult.exceptionOrNull()) - assertInstanceOf(JWTVerificationException::class.java, userIdResult.exceptionOrNull()) - assertInstanceOf(JWTVerificationException::class.java, permissionsResult.exceptionOrNull()) - } - - @Test - fun `Result API should handle wrong issuer and audience consistently`() { - // Arrange - val wrongConfigService = JwtService(testSecret, "wrong-issuer", "wrong-audience") - val wrongToken = wrongConfigService.generateToken("user-123", "test", emptyList()) - - // Act - val validateResult = jwtService.validateToken(wrongToken) - val userIdResult = jwtService.getUserIdFromToken(wrongToken) - val permissionsResult = jwtService.getPermissionsFromToken(wrongToken) - - // Assert - All should consistently fail - assertTrue(validateResult.isFailure) - assertTrue(userIdResult.isFailure) - assertTrue(permissionsResult.isFailure) - - // All exceptions should be JWT verification exceptions - assertInstanceOf(JWTVerificationException::class.java, validateResult.exceptionOrNull()) - assertInstanceOf(JWTVerificationException::class.java, userIdResult.exceptionOrNull()) - assertInstanceOf(JWTVerificationException::class.java, permissionsResult.exceptionOrNull()) - } - - // ========== Result API Interoperability Tests ========== - - @Test - fun `Result API should work well with Kotlin standard library`() { - // Arrange - val validToken = jwtService.generateToken("user-123", "test", listOf(BerechtigungE.PERSON_READ, BerechtigungE.PFERD_CREATE)) - val invalidToken = "invalid.token" - - // Act & Assert - Test integration with Kotlin stdlib - - // Use with let - val letResult = jwtService.validateToken(validToken).getOrNull()?.let { "Token is valid: $it" } - assertEquals("Token is valid: true", letResult) - - // Use with also - var sideEffectCalled = false - jwtService.getUserIdFromToken(validToken).also { result -> - if (result.isSuccess) sideEffectCalled = true - } - assertTrue(sideEffectCalled) - - // Use with takeIf - val conditionalResult = jwtService.getPermissionsFromToken(validToken) - .getOrNull() - ?.takeIf { it.isNotEmpty() } - assertNotNull(conditionalResult) - assertEquals(2, conditionalResult!!.size) - - // Use with run - val runResult = jwtService.validateToken(invalidToken).run { - if (isFailure) "Failed as expected" else "Unexpected success" - } - assertEquals("Failed as expected", runResult) - } - - @Test - fun `Result API should support functional programming patterns`() { - // Arrange - val token = jwtService.generateToken("user-123", "test", listOf(BerechtigungE.PERSON_READ)) - - // Act & Assert - Functional patterns - - // Map transformations - val transformedValidation = jwtService.validateToken(token) - .map { if (it) 1 else 0 } - .getOrElse { -1 } - assertEquals(1, transformedValidation) - - // Filter-like behavior - val hasReadPermission = jwtService.getPermissionsFromToken(token) - .map { permissions -> permissions.contains(BerechtigungE.PERSON_READ) } - .getOrElse { false } - assertTrue(hasReadPermission) - - // Combine multiple Results - val combinedCheck = jwtService.validateToken(token).isSuccess && - jwtService.getUserIdFromToken(token).isSuccess && - jwtService.getPermissionsFromToken(token).isSuccess - assertTrue(combinedCheck) - } -} diff --git a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/SecurityTest.kt b/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/SecurityTest.kt deleted file mode 100644 index fe06f730..00000000 --- a/infrastructure/auth/auth-client/src/test/kotlin/at/mocode/infrastructure/auth/client/SecurityTest.kt +++ /dev/null @@ -1,390 +0,0 @@ -package at.mocode.infrastructure.auth.client - -import at.mocode.infrastructure.auth.client.model.BerechtigungE -import com.auth0.jwt.exceptions.JWTVerificationException -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertTimeoutPreemptively -import org.springframework.test.annotation.DirtiesContext -import java.time.Duration -import kotlin.time.Duration.Companion.minutes - -/** - * Security-focused tests for JWT handling. - * Tests against common JWT vulnerabilities and security attack vectors. - */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -class SecurityTest { - - private lateinit var jwtService: JwtService - private val testSecret = "a-very-long-and-secure-test-secret-that-is-at-least-512-bits-long-for-hmac512" - private val testIssuer = "test-issuer" - private val testAudience = "test-audience" - - @BeforeEach - fun setUp() { - jwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.minutes - ) - } - - // ========== Signature Tampering Tests ========== - - @Test - @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) - fun `should reject tokens with tampered signatures`() { - // Arrange - neue JwtService-Instanz für vollständige Isolation - val isolatedJwtService = JwtService( - secret = testSecret, - issuer = testIssuer, - audience = testAudience, - expiration = 60.minutes - ) - - // Arrange - val validToken = isolatedJwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - val tokenParts = validToken.split(".") - - // Validierung der Token-Struktur - assertEquals(3, tokenParts.size, "JWT should have exactly 3 parts") - assertTrue(tokenParts[2].isNotEmpty(), "Signature part should not be empty") - - // Tamper with the signature by changing the last character - val tamperedSignature = tokenParts[2].dropLast(1) + "X" - val tamperedToken = "${tokenParts[0]}.${tokenParts[1]}.$tamperedSignature" - - // Sicherstellen, dass Signatur tatsächlich verändert wurde - assertNotEquals(tokenParts[2], tamperedSignature, "Signature should be different after tampering") - - // Act - val result = isolatedJwtService.validateToken(tamperedToken) - - // Assert - Erweiterte Validierung - assertTrue(result.isFailure, "Tampered token should be rejected") - val exception = result.exceptionOrNull() - assertNotNull(exception, "Exception should be present for failed validation") - assertInstanceOf( - JWTVerificationException::class.java, exception, - "Exception should be JWTVerificationException, but was: ${exception?.javaClass?.simpleName}" - ) - - // Zusätzliche Sicherheitsüberprüfung: Original Token sollte noch gültig sein - val originalResult = isolatedJwtService.validateToken(validToken) - assertTrue(originalResult.isSuccess, "Original valid token should still be valid") - } - - @Test - @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) - fun `should reject tokens with completely different signatures`() { - // Isolierte Instanzen verwenden - val isolatedJwtService1 = JwtService(testSecret, testIssuer, testAudience, expiration = 60.minutes) - val isolatedJwtService2 = JwtService(testSecret, testIssuer, testAudience, expiration = 60.minutes) - - // Arrange - val validToken = isolatedJwtService1.generateToken("user-123", "testuser", emptyList()) - val anotherValidToken = isolatedJwtService2.generateToken("user-456", "anotheruser", emptyList()) - - val tokenParts1 = validToken.split(".") - val tokenParts2 = anotherValidToken.split(".") - - // Mix signature from different token - val mixedToken = "${tokenParts1[0]}.${tokenParts1[1]}.${tokenParts2[2]}" - - // Act - val result = isolatedJwtService1.validateToken(mixedToken) - - // Assert - assertTrue(result.isFailure) - assertInstanceOf(JWTVerificationException::class.java, result.exceptionOrNull()) - } - - @Test - fun `should reject tokens with extended expiration time`() { - // This test simulates an attacker trying to extend the token's validity - // by manipulating the payload (even though it will break the signature) - - // Arrange - val validToken = jwtService.generateToken("user-123", "testuser", emptyList()) - val tokenParts = validToken.split(".") - - // Try to use a different payload with extended expiration - // (This will fail signature validation, which is the expected behavior) - val anotherService = JwtService(testSecret, testIssuer, testAudience, expiration = 24.minutes) - val longValidToken = anotherService.generateToken("user-123", "testuser", emptyList()) - val longValidParts = longValidToken.split(".") - - val tamperedToken = "${longValidParts[0]}.${longValidParts[1]}.${tokenParts[2]}" - - // Act - val result = jwtService.validateToken(tamperedToken) - - // Assert - assertTrue(result.isFailure) - } - - // ========== Timing Attack Resistance Tests ========== - - @Test - fun `token validation should be resistant to timing attacks`() { - // Arrange - val validToken = jwtService.generateToken("user-123", "testuser", emptyList()) - val invalidTokens = listOf( - "invalid.token.signature", - validToken.dropLast(5) + "wrong", - "completely.wrong.token", - "" - ) - - // Measure validation times for valid and invalid tokens - val validationTimes = mutableListOf() - - // Act - Test multiple times to get consistent timing measurements - repeat(10) { - // Valid token - val start1 = System.nanoTime() - jwtService.validateToken(validToken) - val end1 = System.nanoTime() - validationTimes.add(end1 - start1) - - // Invalid tokens - invalidTokens.forEach { invalidToken -> - val start2 = System.nanoTime() - jwtService.validateToken(invalidToken) - val end2 = System.nanoTime() - validationTimes.add(end2 - start2) - } - } - - // Assert - All validation operations should complete reasonably quickly - // (This is not a perfect timing attack test but ensures no obvious timing differences) - validationTimes.forEach { time -> - assertTrue(time < 50_000_000, "Token validation should complete within 50ms (was ${time}ns)") - } - } - - @Test - fun `validation should complete under consistent time limits`() { - // Arrange - val tokens = (1..20).map { - jwtService.generateToken("user-$it", "testuser$it", listOf(BerechtigungE.PERSON_READ)) - } - - // Act & Assert - Each validation should complete within reasonable time - tokens.forEach { token -> - assertTimeoutPreemptively(Duration.ofMillis(100)) { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - } - } - - // ========== JWT Vulnerability Tests (Based on Common CVEs) ========== - - @Test - fun `should validate against algorithm confusion attack`() { - // This test ensures our service doesn't accept tokens with different algorithms - // Common attack: changing algorithm from RS256 to HS256 in the header - - // Arrange - val validToken = jwtService.generateToken("user-123", "testuser", emptyList()) - val tokenParts = validToken.split(".") - - // Try to create a token with a manipulated header (algorithm confusion) - // In practice, this would require crafting a specific header, but our implementation - // should reject any token that doesn't match our configured algorithm - val manipulatedHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" // RS256 instead of HS512 - val manipulatedToken = "$manipulatedHeader.${tokenParts[1]}.${tokenParts[2]}" - - // Act - val result = jwtService.validateToken(manipulatedToken) - - // Assert - assertTrue(result.isFailure) - } - - @Test - fun `should reject tokens without proper structure`() { - // Test malformed tokens that don't follow the JWT structure - val malformedTokens = listOf( - "not.a.jwt", - "only.two.parts", - "too.many.parts.here.extra", - ".empty.first.", - "first..third", - "first.second.", - "", - "single-string-no-dots" - ) - - malformedTokens.forEach { malformedToken -> - val result = jwtService.validateToken(malformedToken) - assertTrue(result.isFailure, "Malformed token '$malformedToken' should be rejected") - } - } - - @Test - fun `should handle extremely long tokens without hanging`() { - // Test against DoS attacks using extremely long tokens - val longString = "a".repeat(10000) - val longTokens = listOf( - "$longString.valid.token", - "valid.$longString.token", - "valid.token.$longString", - "$longString.$longString.$longString" - ) - - longTokens.forEach { longToken -> - assertTimeoutPreemptively(Duration.ofSeconds(1)) { - val result = jwtService.validateToken(longToken) - assertTrue(result.isFailure, "Long token should be rejected quickly") - } - } - } - - // ========== Token Replay Attack Tests ========== - - @Test - fun `should handle multiple validations of same token consistently`() { - // Test that the same token always produces the same validation result - // This ensures no state is maintained that could be exploited - - val token = jwtService.generateToken("user-123", "testuser", listOf(BerechtigungE.PERSON_READ)) - - repeat(10) { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess, "Same token should always validate successfully") - - val userId = jwtService.getUserIdFromToken(token) - assertEquals("user-123", userId.getOrNull()) - - val permissions = jwtService.getPermissionsFromToken(token) - assertEquals(1, permissions.getOrElse { emptyList() }.size) - } - } - - // ========== Input Validation Security Tests ========== - - @Test - fun `should handle special characters and injection attempts`() { - // Test with various special characters that might cause issues - val specialUserIds = listOf( - "user'; DROP TABLE users; --", - "user", - "user\n\r\t", - "user\u0000null", - "user${'\u0001'}control", - "../../../etc/passwd" - ) - - specialUserIds.forEach { specialUserId -> - val token = jwtService.generateToken(specialUserId, "testuser", emptyList()) - val result = jwtService.getUserIdFromToken(token) - - assertTrue(result.isSuccess) - assertEquals( - specialUserId, result.getOrNull(), - "Special characters in user ID should be preserved exactly" - ) - } - } - - @Test - fun `should handle unicode and international characters`() { - // Test with international characters to ensure proper encoding/decoding - val internationalUserIds = listOf( - "用户123", // Chinese - "utilisateur123", // French - "пользователь123", // Russian - "مستخدم123", // Arabic - "🧑‍💻user123" // Emoji - ) - - internationalUserIds.forEach { userId -> - val token = jwtService.generateToken(userId, "testuser", emptyList()) - val result = jwtService.getUserIdFromToken(token) - - assertTrue(result.isSuccess) - assertEquals( - userId, result.getOrNull(), - "International characters should be handled correctly" - ) - } - } - - // ========== Rate Limiting Simulation Tests ========== - - @Test - fun `should handle high frequency validation requests`() { - // Simulate high-frequency validation to ensure no memory leaks or performance degradation - val token = jwtService.generateToken("user-123", "testuser", emptyList()) - - val startTime = System.currentTimeMillis() - repeat(1000) { - val result = jwtService.validateToken(token) - assertTrue(result.isSuccess) - } - val endTime = System.currentTimeMillis() - - // Should complete 1000 validations in a reasonable time (less than 5 seconds) - assertTrue( - endTime - startTime < 5000, - "1000 token validations should complete within 5 seconds" - ) - } - - // ========== Memory Safety Tests ========== - - @Test - fun `should not leak sensitive information in error messages`() { - // Ensure that error messages don't contain sensitive information - val invalidToken = "invalid.token.here" - val result = jwtService.validateToken(invalidToken) - - assertTrue(result.isFailure) - val exception = result.exceptionOrNull() - assertNotNull(exception) - - // Error message should not contain the secret or other sensitive information - val errorMessage = exception!!.message ?: "" - assertFalse( - errorMessage.contains(testSecret), - "Error message should not contain the secret" - ) - assertFalse( - errorMessage.contains("HMAC"), - "Error message should not reveal internal algorithm details" - ) - } - - @Test - @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) - fun `should handle concurrent validation requests safely`() { - // Thread-safe JwtService-Instanz - val threadSafeJwtService = JwtService(testSecret, testIssuer, testAudience, expiration = 60.minutes) - val token = threadSafeJwtService.generateToken("user-123", "testuser", emptyList()) - val results = mutableListOf() - - - val threads = (1..10).map { threadIndex -> - Thread { - repeat(100) { - val result = jwtService.validateToken(token) - synchronized(results) { - results.add(result.isSuccess) - } - } - } - } - - threads.forEach { it.start() } - threads.forEach { it.join() } - - // All validations should succeed - assertEquals(1000, results.size) - assertTrue(results.all { it }, "All concurrent validations should succeed") - } -} diff --git a/infrastructure/auth/auth-server/build.gradle.kts b/infrastructure/auth/auth-server/build.gradle.kts index 5cd07666..e357621d 100644 --- a/infrastructure/auth/auth-server/build.gradle.kts +++ b/infrastructure/auth/auth-server/build.gradle.kts @@ -25,8 +25,6 @@ dependencies { implementation(platform(projects.platform.platformBom)) // Stellt gemeinsame Abhängigkeiten bereit. implementation(projects.platform.platformDependencies) - // Nutzt die Client-Logik für die Kommunikation mit Keycloak. - implementation(projects.infrastructure.auth.authClient) // Spring Boot Starter für einen Web-Service. // OPTIMIERUNG: Verwendung des `spring-boot-essentials`-Bundles. implementation(libs.bundles.spring.boot.essentials) diff --git a/infrastructure/auth/auth-server/src/main/kotlin/at/mocode/infrastructure/auth/config/AuthServerConfiguration.kt b/infrastructure/auth/auth-server/src/main/kotlin/at/mocode/infrastructure/auth/config/AuthServerConfiguration.kt index 6a1f35a5..803ff1ed 100644 --- a/infrastructure/auth/auth-server/src/main/kotlin/at/mocode/infrastructure/auth/config/AuthServerConfiguration.kt +++ b/infrastructure/auth/auth-server/src/main/kotlin/at/mocode/infrastructure/auth/config/AuthServerConfiguration.kt @@ -1,55 +1,12 @@ package at.mocode.infrastructure.auth.config -import at.mocode.infrastructure.auth.client.JwtService -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.validation.annotation.Validated -import jakarta.validation.constraints.Min -import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Size -import kotlin.time.Duration.Companion.minutes /** * Spring-Konfiguration für das Auth-Server-Modul. - * Stellt die notwendigen Beans und Einstellungen für JWT-Verarbeitung und Authentifizierung bereit. + * + * Note: JWT handling is now fully delegated to Keycloak via OAuth2 Resource Server. + * This auth-server focuses on user management through Keycloak Admin Client. */ @Configuration -@EnableConfigurationProperties(AuthServerConfiguration.JwtProperties::class) -class AuthServerConfiguration { - - /** - * Erstellt einen JwtService-Bean mit Konfiguration aus den Application Properties. - */ - @Bean - fun jwtService(jwtProperties: JwtProperties): JwtService { - // Basic safeguard: warn if default secret is used - if (jwtProperties.secret == "default-secret-for-development-only-please-change-in-production") { - System.err.println("[SECURITY WARNING] Using default JWT secret – DO NOT use this in production!") - } - return JwtService( - secret = jwtProperties.secret, - issuer = jwtProperties.issuer, - audience = jwtProperties.audience, - expiration = jwtProperties.expiration.minutes - ) - } - - /** - * Konfigurationseigenschaften für JWT-Einstellungen. - */ - @ConfigurationProperties(prefix = "auth.jwt") - @Validated - data class JwtProperties( - @field:NotBlank - @field:Size(min = 32, message = "JWT secret must be at least 32 characters for HMAC512") - val secret: String = "default-secret-for-development-only-please-change-in-production", - @field:NotBlank - val issuer: String = "meldestelle-auth-server", - @field:NotBlank - val audience: String = "meldestelle-services", - @field:Min(1) - val expiration: Long = 60 // minutes - ) -} +class AuthServerConfiguration diff --git a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerApplicationTest.kt b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerApplicationTest.kt index 46ab5df4..7ac8a1d4 100644 --- a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerApplicationTest.kt +++ b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerApplicationTest.kt @@ -1,13 +1,16 @@ package at.mocode.infrastructure.auth -import at.mocode.infrastructure.auth.client.JwtService import at.mocode.infrastructure.auth.config.AuthServerConfiguration -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test /** * Basic tests for the Auth Server application and configuration. - * These tests verify the application structure and basic functionality without requiring full Spring context. + * These tests verify the application structure without requiring full Spring context. + * + * Note: Custom JWT handling has been removed. Authentication is now fully handled + * by Keycloak via OAuth2 Resource Server. */ class AuthServerApplicationTest { @@ -30,43 +33,14 @@ class AuthServerApplicationTest { } @Test - fun `auth server configuration should create JWT service bean`() { - // Arrange - val config = AuthServerConfiguration() - val jwtProperties = AuthServerConfiguration.JwtProperties( - secret = "test-secret-for-testing-only-at-least-512-bits-long-for-hmac512", - issuer = "test-issuer", - audience = "test-audience", - expiration = 60 - ) - - // Act - val jwtService = config.jwtService(jwtProperties) - - // Assert - assertNotNull(jwtService) - assertInstanceOf(JwtService::class.java, jwtService) - - // Test that the service can generate and validate tokens - val token = jwtService.generateToken("test-user", "testuser", emptyList()) - assertNotNull(token) - assertTrue(token.isNotEmpty()) - - val validationResult = jwtService.validateToken(token) - assertTrue(validationResult.isSuccess) - assertEquals(true, validationResult.getOrNull()) - } - - @Test - fun `JWT properties should have sensible defaults`() { + fun `auth server configuration should be present`() { // Arrange & Act - val defaultProperties = AuthServerConfiguration.JwtProperties() + val config = AuthServerConfiguration() // Assert - assertNotNull(defaultProperties.secret) - assertTrue(defaultProperties.secret.isNotEmpty()) - assertEquals("meldestelle-auth-server", defaultProperties.issuer) - assertEquals("meldestelle-services", defaultProperties.audience) - assertEquals(60L, defaultProperties.expiration) + assertNotNull(config) + assertTrue(config::class.java.isAnnotationPresent(org.springframework.context.annotation.Configuration::class.java)) { + "AuthServerConfiguration should be annotated with @Configuration" + } } } diff --git a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerIntegrationTest.kt b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerIntegrationTest.kt index 62d475f8..42fcc2a4 100644 --- a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerIntegrationTest.kt +++ b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/AuthServerIntegrationTest.kt @@ -1,10 +1,8 @@ package at.mocode.infrastructure.auth -import at.mocode.infrastructure.auth.client.JwtService -import at.mocode.infrastructure.auth.client.model.BerechtigungE import at.mocode.infrastructure.auth.config.AuthServerConfiguration -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -13,21 +11,16 @@ import org.springframework.test.context.TestPropertySource /** * Minimal integration tests for the Auth Server. - * Tests essential functionality without full Spring Boot context complexity. - * Focuses on core service integration and configuration validation. * - * This implements "Option 1: Minimale Integration Tests" focusing on essentials - * without vollständige Spring Boot Konfiguration. + * Note: Custom JWT handling has been removed. Authentication is now fully handled + * by Keycloak via OAuth2 Resource Server. This test verifies the basic Spring + * context loads correctly. */ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [AuthServerConfiguration::class] ) @TestPropertySource(properties = [ - "auth.jwt.secret=test-secret-for-testing-only-at-least-512-bits-long-for-hmac512-algorithm", - "auth.jwt.issuer=test-issuer", - "auth.jwt.audience=test-audience", - "auth.jwt.expiration=60", "spring.main.web-application-type=none", "logging.level.org.springframework.security=WARN" ]) @@ -36,22 +29,6 @@ class AuthServerIntegrationTest { @Autowired private lateinit var applicationContext: ApplicationContext - @Autowired - private lateinit var jwtService: JwtService - - private lateinit var testToken: String - - @BeforeEach - fun setUp() { - testToken = jwtService.generateToken( - userId = "test-user-123", - username = "testuser", - permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.VEREIN_READ) - ) - } - - // ========== Core Service Integration Tests ========== - @Test fun `application context should load with minimal configuration`() { // Verify that the Spring context loads successfully @@ -63,138 +40,17 @@ class AuthServerIntegrationTest { } @Test - fun `JwtService should be properly configured as Spring bean`() { - // Verify that JwtService is available as a Spring bean - assertTrue(applicationContext.containsBean("jwtService")) - assertNotNull(jwtService) - assertInstanceOf(JwtService::class.java, jwtService) - - println("[DEBUG_LOG] JwtService bean configured successfully") - } - - @Test - fun `JWT service should generate valid tokens`() { - // Test token generation functionality - val token = jwtService.generateToken( - userId = "integration-test-user", - username = "inttest", - permissions = listOf(BerechtigungE.PERSON_CREATE, BerechtigungE.PFERD_READ) - ) - - assertNotNull(token) - assertTrue(token.isNotEmpty()) - - // Verify token can be validated - val validationResult = jwtService.validateToken(token) - assertTrue(validationResult.isSuccess) - assertEquals(true, validationResult.getOrNull()) - - println("[DEBUG_LOG] Token generated and validated successfully") - } - - @Test - fun `JWT service should extract user information correctly`() { - // Test user ID extraction - val userIdResult = jwtService.getUserIdFromToken(testToken) - assertTrue(userIdResult.isSuccess) - assertEquals("test-user-123", userIdResult.getOrNull()) - - // Test permissions extraction - val permissionsResult = jwtService.getPermissionsFromToken(testToken) - assertTrue(permissionsResult.isSuccess) - val permissions = permissionsResult.getOrNull()!! - assertEquals(2, permissions.size) - assertTrue(permissions.contains(BerechtigungE.PERSON_READ)) - assertTrue(permissions.contains(BerechtigungE.VEREIN_READ)) - - println("[DEBUG_LOG] User information extracted correctly") - println("[DEBUG_LOG] User ID: ${userIdResult.getOrNull()}") - println("[DEBUG_LOG] Permissions: $permissions") - } - - @Test - fun `JWT service should handle invalid tokens properly`() { - val invalidToken = "invalid.jwt.token" - - // Validation should fail - val validationResult = jwtService.validateToken(invalidToken) - assertTrue(validationResult.isFailure) - - // User ID extraction should fail - val userIdResult = jwtService.getUserIdFromToken(invalidToken) - assertTrue(userIdResult.isFailure) - - // Permissions extraction should fail - val permissionsResult = jwtService.getPermissionsFromToken(invalidToken) - assertTrue(permissionsResult.isFailure) - - println("[DEBUG_LOG] Invalid token handling works correctly") - } - - // ========== Configuration Validation Tests ========== - - @Test - fun `configuration properties should be properly loaded`() { - // Test that JWT configuration is loaded correctly - val jwtProperties = applicationContext.getBean(AuthServerConfiguration.JwtProperties::class.java) - assertNotNull(jwtProperties) - assertEquals("test-issuer", jwtProperties.issuer) - assertEquals("test-audience", jwtProperties.audience) - assertEquals(60L, jwtProperties.expiration) - - println("[DEBUG_LOG] Configuration properties loaded correctly") - println("[DEBUG_LOG] Issuer: ${jwtProperties.issuer}") - println("[DEBUG_LOG] Audience: ${jwtProperties.audience}") - println("[DEBUG_LOG] Expiration: ${jwtProperties.expiration}") - } - - @Test - fun `essential beans should be properly configured`() { - // Verify that essential beans for auth functionality are available + fun `configuration bean should be present`() { + // Verify that essential beans are available val beanNames = applicationContext.beanDefinitionNames.toList() - // Check for JWT service bean - assertTrue(applicationContext.containsBean("jwtService")) { - "JwtService bean should be configured" - } - // Check for configuration bean assertTrue(beanNames.any { it.contains("authServerConfiguration") }) { "AuthServerConfiguration bean should be configured" } println("[DEBUG_LOG] Essential beans configured successfully") - println("[DEBUG_LOG] Auth-related beans: ${beanNames.filter { it.contains("jwt") || it.contains("auth") }}") - } - - @Test - fun `JWT configuration integration should work end-to-end`() { - // Test the complete flow from configuration to token operations - val userId = "end-to-end-test" - val username = "e2etest" - val permissions = listOf(BerechtigungE.PERSON_READ, BerechtigungE.PERSON_CREATE) - - // Generate token - val token = jwtService.generateToken(userId, username, permissions) - assertNotNull(token) - assertTrue(token.isNotEmpty()) - - // Validate token - val isValid = jwtService.validateToken(token) - assertTrue(isValid.isSuccess) - - // Extract and verify data - val extractedUserId = jwtService.getUserIdFromToken(token).getOrNull() - val extractedPermissions = jwtService.getPermissionsFromToken(token).getOrElse { emptyList() } - - assertEquals(userId, extractedUserId) - assertEquals(2, extractedPermissions.size) - assertTrue(extractedPermissions.containsAll(permissions)) - - println("[DEBUG_LOG] End-to-end test completed successfully") - println("[DEBUG_LOG] Token validation: ${isValid.isSuccess}") - println("[DEBUG_LOG] Extracted user: $extractedUserId") - println("[DEBUG_LOG] Extracted permissions: $extractedPermissions") + println("[DEBUG_LOG] Auth-related beans: ${beanNames.filter { it.contains("auth") }}") } @Test @@ -217,48 +73,4 @@ class AuthServerIntegrationTest { println("[DEBUG_LOG] Total bean count: $beanCount") println("[DEBUG_LOG] Web-related beans: $webBeans") } - - // ========== Service Functionality Tests ========== - - @Test - fun `JWT service should handle different permission combinations`() { - // Test various permission combinations - val testCases = listOf( - emptyList(), - listOf(BerechtigungE.PERSON_READ), - listOf(BerechtigungE.PERSON_READ, BerechtigungE.PERSON_CREATE), - BerechtigungE.entries - ) - - testCases.forEach { permissions -> - val token = jwtService.generateToken("test-user", "test", permissions) - val validationResult = jwtService.validateToken(token) - val extractedPermissions = jwtService.getPermissionsFromToken(token).getOrElse { emptyList() } - - assertTrue(validationResult.isSuccess) - assertEquals(permissions.size, extractedPermissions.size) - assertTrue(extractedPermissions.containsAll(permissions)) - } - - println("[DEBUG_LOG] Different permission combinations handled correctly") - } - - @Test - fun `JWT service should be thread-safe for concurrent access`() { - // Test concurrent token operations - val threads = (1..5).map { threadIndex -> - Thread { - repeat(10) { iteration -> - val token = jwtService.generateToken("user-$threadIndex-$iteration", "test", listOf(BerechtigungE.PERSON_READ)) - val isValid = jwtService.validateToken(token).isSuccess - assertTrue(isValid) - } - } - } - - threads.forEach { it.start() } - threads.forEach { it.join() } - - println("[DEBUG_LOG] Concurrent access test completed successfully") - } } diff --git a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/config/TestConfiguration.kt b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/config/TestConfiguration.kt index 4a71c7a1..22c09bd3 100644 --- a/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/config/TestConfiguration.kt +++ b/infrastructure/auth/auth-server/src/test/kotlin/at/mocode/infrastructure/auth/config/TestConfiguration.kt @@ -1,29 +1,13 @@ package at.mocode.infrastructure.auth.config -import at.mocode.infrastructure.auth.client.JwtService import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Primary -import kotlin.time.Duration.Companion.minutes /** * Test configuration for Auth Server integration tests. - * Provides minimal bean configuration needed for tests to run. + * + * Note: Custom JWT handling has been removed. Authentication is now fully handled + * by Keycloak via OAuth2 Resource Server. This configuration class is kept as a + * placeholder for future test-specific beans if needed. */ @TestConfiguration -class AuthServerTestConfiguration { - - /** - * Provides a JwtService bean for testing with test-specific configuration. - */ - @Bean - @Primary - fun testJwtService(): JwtService { - return JwtService( - secret = "test-secret-for-testing-only-at-least-512-bits-long-for-hmac512-algorithm", - issuer = "test-issuer", - audience = "test-audience", - expiration = 60.minutes - ) - } -} +class AuthServerTestConfiguration diff --git a/infrastructure/gateway/build.gradle.kts b/infrastructure/gateway/build.gradle.kts index 0e3f22a9..1d7c7225 100644 --- a/infrastructure/gateway/build.gradle.kts +++ b/infrastructure/gateway/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { // === Core Dependencies === implementation(projects.core.coreUtils) implementation(projects.platform.platformDependencies) - implementation(projects.infrastructure.auth.authClient) implementation(projects.infrastructure.monitoring.monitoringClient) // === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN === @@ -31,7 +30,6 @@ dependencies { implementation(libs.bundles.logging) implementation(libs.bundles.jackson.kotlin) implementation(project(":infrastructure:event-store:redis-event-store")) - implementation(project(":infrastructure:event-store:redis-event-store")) // === Test Dependencies === testImplementation(projects.platform.platformTesting) diff --git a/infrastructure/gateway/src/main/resources/application-keycloak.yml b/infrastructure/gateway/src/main/resources/application-keycloak.yml index a748b928..4b05ccd5 100644 --- a/infrastructure/gateway/src/main/resources/application-keycloak.yml +++ b/infrastructure/gateway/src/main/resources/application-keycloak.yml @@ -26,16 +26,3 @@ keycloak: client-id: ${KEYCLOAK_CLIENT_ID:api-gateway} public-client: false bearer-only: true - -# Gateway-spezifische Sicherheitskonfiguration -gateway: - security: - jwt: - # Enable JWT validation via Spring Security OAuth2 Resource Server - enabled: true - keycloak: - # Custom JWT filter DISABLED - using Spring Security oauth2ResourceServer instead - # This prevents duplicate authentication and ensures proper JWT signature validation - enabled: false - server-url: ${KEYCLOAK_SERVER_URL:http://keycloak:8080} - realm: ${KEYCLOAK_REALM:meldestelle} diff --git a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt index 13d13371..173abe3c 100644 --- a/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt +++ b/infrastructure/gateway/src/test/kotlin/at/mocode/infrastructure/gateway/KeycloakGatewayIntegrationTest.kt @@ -11,7 +11,7 @@ import org.springframework.test.context.TestPropertySource * Simplified integration test for Keycloak Gateway integration. * This test verifies that the Spring context can initialize properly with Keycloak configuration * without requiring actual Testcontainers, focusing on resolving the OAuth2 ResourceServer - * auto-configuration timing issue. + * autoconfiguration timing issue. */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("keycloak-integration-test") diff --git a/settings.gradle.kts b/settings.gradle.kts index deaf6a16..9e914655 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,7 +43,6 @@ include(":platform:platform-testing") // Infrastructure modules include(":infrastructure:gateway") -include(":infrastructure:auth:auth-client") include(":infrastructure:auth:auth-server") include(":infrastructure:messaging:messaging-client") include(":infrastructure:messaging:messaging-config")