diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt index 1861536a4..0a29d2bd8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt @@ -11,7 +11,7 @@ const val ROLE_FILE_DOWNLOAD = "FILE_DOWNLOAD" const val ROLE_PAGE_STREAMING = "PAGE_STREAMING" data class KomgaUser( - @Email + @Email(regexp = ".+@.+\\..+") @NotBlank val email: String, @NotBlank diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ClaimController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ClaimController.kt index 1383f0627..132ac95f4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ClaimController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ClaimController.kt @@ -28,7 +28,7 @@ class ClaimController( @PostMapping fun claimAdmin( - @Email @RequestHeader("X-Komga-Email") email: String, + @Email(regexp = ".+@.+\\..+") @RequestHeader("X-Komga-Email") email: String, @NotBlank @RequestHeader("X-Komga-Password") password: String ): UserDto { if (userDetailsLifecycle.countUsers() > 0) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ErrorHandlingControllerAdvice.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ErrorHandlingControllerAdvice.kt new file mode 100644 index 000000000..9a8201e93 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ErrorHandlingControllerAdvice.kt @@ -0,0 +1,41 @@ +package org.gotson.komga.interfaces.rest + +import org.springframework.http.HttpStatus +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.ResponseBody +import org.springframework.web.bind.annotation.ResponseStatus +import javax.validation.ConstraintViolationException + +@ControllerAdvice +class ErrorHandlingControllerAdvice { + @ExceptionHandler(ConstraintViolationException::class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + fun onConstraintValidationException( + e: ConstraintViolationException + ): ValidationErrorResponse = + ValidationErrorResponse( + e.constraintViolations.map { Violation(it.propertyPath.toString(), it.message) } + ) + + @ExceptionHandler(MethodArgumentNotValidException::class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + fun onMethodArgumentNotValidException( + e: MethodArgumentNotValidException + ): ValidationErrorResponse = + ValidationErrorResponse( + e.bindingResult.fieldErrors.map { Violation(it.field, it.defaultMessage) } + ) +} + +data class ValidationErrorResponse( + val violations: List = emptyList() +) + +data class Violation( + val fieldName: String? = null, + val message: String? = null +) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt index 44759904b..88f9e1737 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/dto/UserDto.kt @@ -45,7 +45,7 @@ fun KomgaUser.toWithSharedLibrariesDto() = ) data class UserCreationDto( - @get:Email val email: String, + @get:Email(regexp = ".+@.+\\..+") val email: String, @get:NotBlank val password: String, val roles: List = emptyList() ) { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/ClaimControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/ClaimControllerTest.kt new file mode 100644 index 000000000..367581580 --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/ClaimControllerTest.kt @@ -0,0 +1,34 @@ +package org.gotson.komga.interfaces.rest + +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.post + +@ExtendWith(SpringExtension::class) +@SpringBootTest +@AutoConfigureMockMvc(printOnlyOnFailure = false) +@ActiveProfiles("test") +class ClaimControllerTest( + @Autowired private val mockMvc: MockMvc + +) { + @ParameterizedTest + @ValueSource(strings = ["user", "user@domain"]) + fun `given unclaimed server when claiming with invalid email address then returns bad request`(email: String) { + val password = "password" + + mockMvc.post("/api/v1/claim") { + header("X-Komga-Email", email) + header("X-Komga-Password", password) + }.andExpect { + status { isBadRequest() } + } + } +} diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt index c19d03262..e50a0dfc5 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/rest/UserControllerTest.kt @@ -1,7 +1,9 @@ package org.gotson.komga.interfaces.rest -import org.junit.jupiter.api.Test +import org.gotson.komga.domain.model.ROLE_ADMIN import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest @@ -9,26 +11,27 @@ import org.springframework.http.MediaType import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.patch +import org.springframework.test.web.servlet.post @ExtendWith(SpringExtension::class) @SpringBootTest @AutoConfigureMockMvc(printOnlyOnFailure = false) -@ActiveProfiles("demo", "test") +@ActiveProfiles("test") class UserControllerTest( @Autowired private val mockMvc: MockMvc ) { - @Test - @WithMockCustomUser - fun `given demo profile is active when a user tries to update its password via api then returns forbidden`() { - val jsonString = """{"password":"new"}""" + @ParameterizedTest + @ValueSource(strings = ["user", "user@domain"]) + @WithMockCustomUser(roles = [ROLE_ADMIN]) + fun `when creating a user with invalid email then returns bad request`(email: String) { + val jsonString = """{"email":"$email","password":"password"}""" - mockMvc.patch("/api/v1/users/me/password") { + mockMvc.post("/api/v1/users") { contentType = MediaType.APPLICATION_JSON content = jsonString }.andExpect { - status { isForbidden() } + status { isBadRequest() } } } }